mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Add txn support to existing routes
This commit is contained in:
+422
-179
@@ -13,6 +13,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Parameter;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Operation;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Attributes;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Collections;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Databases;
|
||||
@@ -3492,17 +3493,18 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
]
|
||||
)
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('documentId', '', new CustomId(), 'Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true)
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.')
|
||||
->param('documentId', '', new CustomId(), 'Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true)
|
||||
->param('data', [], new JSON(), 'Document data as JSON object.', true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of documents data as JSON objects.', true, ['plan'])
|
||||
->param('transactionId', null, new UID(), 'Transaction ID for staging operation.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->action(function (string $databaseId, ?string $documentId, string $collectionId, string|array|null $data, ?array $permissions, ?array $documents, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage) {
|
||||
->action(function (string $databaseId, string $collectionId, ?string $documentId, string|array|null $data, ?array $permissions, ?array $documents, ?string $transactionId, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage) {
|
||||
$data = \is_string($data)
|
||||
? \json_decode($data, true)
|
||||
: $data;
|
||||
@@ -3565,6 +3567,16 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk create is not supported for collections with relationship attributes');
|
||||
}
|
||||
|
||||
if (!empty($transactionId)) {
|
||||
$transaction = $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
if ($transaction->getAttribute('status') !== 'pending') {
|
||||
throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending');
|
||||
}
|
||||
}
|
||||
|
||||
$setPermissions = function (Document $document, ?array $permissions) use ($user, $isAPIKey, $isPrivilegedUser, $isBulk) {
|
||||
$allowedPermissions = [
|
||||
Database::PERMISSION_READ,
|
||||
@@ -3727,26 +3739,46 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
return $document;
|
||||
}, $documents);
|
||||
|
||||
try {
|
||||
$dbForProject->createDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$documents
|
||||
);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
} catch (RelationshipException $e) {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage());
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
|
||||
}
|
||||
if (!empty($transactionId)) {
|
||||
$operations = [];
|
||||
foreach ($documents as $document) {
|
||||
$operations[] = new Document([
|
||||
'databaseInternalId' => $database->getSequence(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'transactionInternalId' => $transaction->getSequence(),
|
||||
'action' => 'create',
|
||||
'documentId' => $document->getId(),
|
||||
'data' => $document->getArrayCopy(),
|
||||
]);
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $database);
|
||||
try {
|
||||
$dbForProject->createDocuments('transactionLogs', $operations);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
} catch (RelationshipException $e) {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage());
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$dbForProject->createDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$documents
|
||||
);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
} catch (RelationshipException $e) {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage());
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Add $collectionId and $databaseId for all documents
|
||||
$processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {
|
||||
@@ -3786,12 +3818,20 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
$processDocument($collection, $document);
|
||||
}
|
||||
|
||||
$response->setStatusCode(Response::STATUS_CODE_CREATED);
|
||||
|
||||
if (empty($transactionId)) {
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $database);
|
||||
}
|
||||
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $operations))
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations)); // per collection
|
||||
|
||||
$response->setStatusCode(Response::STATUS_CODE_CREATED);
|
||||
|
||||
if ($isBulk) {
|
||||
$response->dynamic(new Document([
|
||||
'total' => count($documents),
|
||||
@@ -3801,9 +3841,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
return;
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('documentId', $documents[0]->getId())
|
||||
->setEvent('databases.[databaseId].collections.[collectionId].documents.[documentId].create');
|
||||
if (empty($transactionId)) {
|
||||
$queueForEvents
|
||||
->setParam('documentId', $documents[0]->getId())
|
||||
->setEvent('databases.[databaseId].collections.[collectionId].documents.[documentId].create');
|
||||
}
|
||||
|
||||
$response->dynamic(
|
||||
$documents[0],
|
||||
@@ -4244,12 +4286,13 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
->param('documentId', '', new UID(), 'Document ID.')
|
||||
->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('transactionId', null, new UID(), 'Transaction ID for staging operation.', true)
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) {
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) {
|
||||
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
|
||||
|
||||
if (empty($data) && \is_null($permissions)) {
|
||||
@@ -4269,6 +4312,16 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!empty($transactionId)) {
|
||||
$transaction = $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
if ($transaction->getAttribute('status') !== 'pending') {
|
||||
throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending');
|
||||
}
|
||||
}
|
||||
|
||||
// Read permission should not be required for update
|
||||
$document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId));
|
||||
if ($document->isEmpty()) {
|
||||
@@ -4388,20 +4441,31 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $operations))
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations));
|
||||
|
||||
try {
|
||||
$document = $dbForProject->updateDocument(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$document->getId(),
|
||||
$newDocument
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
|
||||
} catch (RelationshipException $e) {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage());
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
|
||||
if (!empty($transactionId)) {
|
||||
$dbForProject->createDocument('transactionLog', new Document([
|
||||
'transactionInternalId' => $transaction->getSequence(),
|
||||
'databaseInternalId' => $database->getSequence(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'documentId' => $document->getId(),
|
||||
'data' => $newDocument->getArrayCopy(),
|
||||
'action' => 'update',
|
||||
]));
|
||||
} else {
|
||||
try {
|
||||
$document = $dbForProject->updateDocument(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$document->getId(),
|
||||
$newDocument
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
|
||||
} catch (RelationshipException $e) {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage());
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Add $collectionId and $databaseId for all documents
|
||||
@@ -4447,13 +4511,15 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
)
|
||||
);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('documentId', $document->getId())
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $database)
|
||||
->setPayload($response->getPayload(), sensitive: $relationships);
|
||||
if (empty($transactionId)) {
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('documentId', $document->getId())
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $database)
|
||||
->setPayload($response->getPayload(), sensitive: $relationships);
|
||||
}
|
||||
|
||||
$response->dynamic($document, Response::MODEL_DOCUMENT);
|
||||
});
|
||||
@@ -4488,12 +4554,13 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
||||
->param('documentId', '', new CustomId(), 'Document ID.')
|
||||
->param('data', [], new JSON(), 'Document data as JSON object. Include all required attributes of the document to be created or updated.')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('transactionId', null, new UID(), 'Transaction ID for staging operation.', true)
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) {
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) {
|
||||
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
|
||||
|
||||
if (empty($data) && \is_null($permissions)) {
|
||||
@@ -4513,6 +4580,16 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!empty($transactionId)) {
|
||||
$transaction = $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
if ($transaction->getAttribute('status') !== 'pending') {
|
||||
throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending');
|
||||
}
|
||||
}
|
||||
|
||||
// Map aggregate permissions into the multiple permissions they represent.
|
||||
$permissions = Permission::aggregate($permissions, [
|
||||
Database::PERMISSION_READ,
|
||||
@@ -4622,26 +4699,36 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $operations))
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations));
|
||||
|
||||
$upserted = [];
|
||||
try {
|
||||
$modified = $dbForProject->createOrUpdateDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
[$newDocument],
|
||||
onNext: function (Document $document) use (&$upserted) {
|
||||
$upserted[] = $document;
|
||||
},
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
|
||||
} catch (RelationshipException $e) {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage());
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
|
||||
if (!empty($transactionId)) {
|
||||
$dbForProject->createDocument('transactionLog', new Document([
|
||||
'transactionInternalId' => $transaction->getSequence(),
|
||||
'databaseInternalId' => $database->getSequence(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'action' => 'update',
|
||||
'data' => $newDocument->getArrayCopy(),
|
||||
]));
|
||||
|
||||
$upserted = $newDocument;
|
||||
} else {
|
||||
try {
|
||||
$dbForProject->createOrUpdateDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
[$newDocument],
|
||||
onNext: function (Document $document) use (&$upserted) {
|
||||
$upserted = $document;
|
||||
},
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
|
||||
} catch (RelationshipException $e) {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage());
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$document = $upserted[0];
|
||||
// Add $collectionId and $databaseId for all documents
|
||||
$processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {
|
||||
$document->setAttribute('$databaseId', $database->getId());
|
||||
@@ -4675,7 +4762,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
||||
}
|
||||
};
|
||||
|
||||
$processDocument($collection, $document);
|
||||
$processDocument($collection, $upserted);
|
||||
|
||||
$relationships = \array_map(
|
||||
fn ($document) => $document->getAttribute('key'),
|
||||
@@ -4685,15 +4772,17 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
||||
)
|
||||
);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('documentId', $document->getId())
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $database)
|
||||
->setPayload($response->getPayload(), sensitive: $relationships);
|
||||
if (!empty($transactionId)) {
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('documentId', $upserted->getId())
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $database)
|
||||
->setPayload($response->getPayload(), sensitive: $relationships);
|
||||
}
|
||||
|
||||
$response->dynamic($document, Response::MODEL_DOCUMENT);
|
||||
$response->dynamic($upserted, Response::MODEL_DOCUMENT);
|
||||
});
|
||||
|
||||
App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId/:attribute/increment')
|
||||
@@ -4727,11 +4816,12 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
->param('attribute', '', new Key(), 'Attribute key.')
|
||||
->param('value', 1, new Numeric(), 'Value to increment the attribute by. The value must be a number.', true)
|
||||
->param('max', null, new Numeric(), 'Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.', true)
|
||||
->param('transactionId', null, new UID(), 'Transaction ID for staging operation.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) {
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, ?string $transactionId, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) {
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
@@ -4742,33 +4832,60 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$document = $dbForProject->increaseDocumentAttribute(
|
||||
collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
id: $documentId,
|
||||
attribute: $attribute,
|
||||
value: $value,
|
||||
max: $max
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT);
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_NOT_FOUND);
|
||||
} catch (LimitException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute "' . $attribute . '" has reached the maximum value of ' . $max);
|
||||
} catch (TypeException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_TYPE_INVALID, 'Attribute "' . $attribute . '" is not a number');
|
||||
if (!empty($transactionId)) {
|
||||
$transaction = $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
if ($transaction->getAttribute('status') !== 'pending') {
|
||||
throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending');
|
||||
}
|
||||
|
||||
$dbForProject->createDocument('transactionLog', new Document([
|
||||
'databaseInternalId' => $database->getSequence(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'transactionInternalId' => $transaction->getSequence(),
|
||||
'documentId' => $documentId,
|
||||
'action' => 'increment',
|
||||
'data' => [
|
||||
'attribute' => $attribute,
|
||||
'value' => $value,
|
||||
'max' => $max,
|
||||
]
|
||||
]));
|
||||
|
||||
$document = $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId);
|
||||
} else {
|
||||
try {
|
||||
$document = $dbForProject->increaseDocumentAttribute(
|
||||
collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
id: $documentId,
|
||||
attribute: $attribute,
|
||||
value: $value,
|
||||
max: $max
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT);
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_NOT_FOUND);
|
||||
} catch (LimitException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute "' . $attribute . '" has reached the maximum value of ' . $max);
|
||||
} catch (TypeException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_TYPE_INVALID, 'Attribute "' . $attribute . '" is not a number');
|
||||
}
|
||||
}
|
||||
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, 1)
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), 1);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collectionId)
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $database);
|
||||
if (!empty($transactionId)) {
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collectionId)
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $database);
|
||||
}
|
||||
|
||||
$response->dynamic($document, Response::MODEL_DOCUMENT);
|
||||
});
|
||||
@@ -4804,11 +4921,24 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
->param('attribute', '', new Key(), 'Attribute key.')
|
||||
->param('value', 1, new Numeric(), 'Value to decrement the attribute by. The value must be a number.', true)
|
||||
->param('min', null, new Numeric(), 'Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.', true)
|
||||
->param('transactionId', null, new UID(), 'Transaction ID for staging operation.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) {
|
||||
->action(function (
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
string $documentId,
|
||||
string $attribute,
|
||||
int|float $value,
|
||||
int|float|null $min,
|
||||
?string $transactionId,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Event $queueForEvents,
|
||||
StatsUsage $queueForStatsUsage
|
||||
) {
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
@@ -4819,33 +4949,64 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$document = $dbForProject->decreaseDocumentAttribute(
|
||||
collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
id: $documentId,
|
||||
attribute: $attribute,
|
||||
value: $value,
|
||||
min: $min
|
||||
if (!empty($transactionId)) {
|
||||
$transaction = $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
if ($transaction->getAttribute('status') !== 'pending') {
|
||||
throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending');
|
||||
}
|
||||
|
||||
$dbForProject->createDocument('transactionLog', new Document([
|
||||
'databaseInternalId' => $database->getSequence(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'transactionInternalId' => $transaction->getSequence(),
|
||||
'documentId' => $documentId,
|
||||
'action' => 'decrement',
|
||||
'data' => [
|
||||
'attribute' => $attribute,
|
||||
'value' => $value,
|
||||
'min' => $min,
|
||||
],
|
||||
]));
|
||||
|
||||
// Fetch current document for response without mutating
|
||||
$document = $dbForProject->getDocument(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$documentId
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT);
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_NOT_FOUND);
|
||||
} catch (LimitException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute "' . $attribute . '" has reached the minimum value of ' . $min);
|
||||
} catch (TypeException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_TYPE_INVALID, 'Attribute "' . $attribute . '" is not a number');
|
||||
} else {
|
||||
try {
|
||||
$document = $dbForProject->decreaseDocumentAttribute(
|
||||
collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
id: $documentId,
|
||||
attribute: $attribute,
|
||||
value: $value,
|
||||
min: $min
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT);
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_NOT_FOUND);
|
||||
} catch (LimitException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute "' . $attribute . '" has reached the minimum value of ' . $min);
|
||||
} catch (TypeException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_TYPE_INVALID, 'Attribute "' . $attribute . '" is not a number');
|
||||
}
|
||||
}
|
||||
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, 1)
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), 1);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collectionId)
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $database);
|
||||
if (empty($transactionId)) {
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collectionId)
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $database);
|
||||
}
|
||||
|
||||
$response->dynamic($document, Response::MODEL_DOCUMENT);
|
||||
});
|
||||
@@ -4878,12 +5039,13 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true)
|
||||
->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, new UID(), 'Transaction ID for staging operation.', true)
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('plan')
|
||||
->action(function (string $databaseId, string $collectionId, string|array $data, array $queries, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan) {
|
||||
->action(function (string $databaseId, string $collectionId, string|array $data, array $queries, ?string $transactionId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan) {
|
||||
$data = \is_string($data)
|
||||
? \json_decode($data, true)
|
||||
: $data;
|
||||
@@ -4911,6 +5073,16 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk update is not supported for collections with relationship attributes');
|
||||
}
|
||||
|
||||
if (!empty($transactionId)) {
|
||||
$transaction = $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
if ($transaction->getAttribute('status') !== 'pending') {
|
||||
throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -4987,11 +5159,12 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of document data as JSON objects. May contain partial documents.', false, ['plan'])
|
||||
->param('transactionId', null, new UID(), 'Transaction ID for staging operation.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('plan')
|
||||
->action(function (string $databaseId, string $collectionId, array $documents, Response $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan) {
|
||||
->action(function (string $databaseId, string $collectionId, array $documents, ?string $transactionId, Response $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan) {
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
@@ -5015,26 +5188,52 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
$documents[$key] = new Document($document);
|
||||
}
|
||||
|
||||
$upserted = [];
|
||||
if (!empty($transactionId)) {
|
||||
$transaction = $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
if ($transaction->getAttribute('status') !== 'pending') {
|
||||
throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending');
|
||||
}
|
||||
|
||||
try {
|
||||
$modified = $dbForProject->createOrUpdateDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$documents,
|
||||
onNext: function (Document $document) use ($plan, &$upserted) {
|
||||
if (\count($upserted) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) {
|
||||
$upserted[] = $document;
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
|
||||
} catch (RelationshipException $e) {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage());
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
|
||||
$operations = [];
|
||||
foreach ($documents as $doc) {
|
||||
$operations[] = new Document([
|
||||
'transactionInternalId' => $transaction->getSequence(),
|
||||
'databaseInternalId' => $database->getSequence(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'action' => 'upsert',
|
||||
'data' => $doc->getArrayCopy(),
|
||||
]);
|
||||
}
|
||||
|
||||
$dbForProject->createDocuments('transactionLogs', $operations);
|
||||
|
||||
$modified = \count($documents);
|
||||
$upserted = $documents;
|
||||
} else {
|
||||
$upserted = [];
|
||||
|
||||
try {
|
||||
$modified = $dbForProject->createOrUpdateDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$documents,
|
||||
onNext: function (Document $document) use ($plan, &$upserted) {
|
||||
if (\count($upserted) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) {
|
||||
$upserted[] = $document;
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
|
||||
} catch (RelationshipException $e) {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage());
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($upserted as $document) {
|
||||
@@ -5081,12 +5280,13 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('documentId', '', new UID(), 'Document ID.')
|
||||
->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.')
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) {
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, ?string $transactionId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) {
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
@@ -5106,15 +5306,33 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
||||
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$dbForProject->deleteDocument(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$documentId
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT);
|
||||
} catch (RestrictedException) {
|
||||
throw new Exception(Exception::DOCUMENT_DELETE_RESTRICTED);
|
||||
if (!empty($transactionId)) {
|
||||
$transaction = $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
if ($transaction->getAttribute('status') !== 'pending') {
|
||||
throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending');
|
||||
}
|
||||
|
||||
$dbForProject->createDocument('transactionLog', new Document([
|
||||
'databaseInternalId' => $database->getSequence(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'transactionInternalId' => $transaction->getSequence(),
|
||||
'documentId' => $document->getId(),
|
||||
'action' => 'delete',
|
||||
]));
|
||||
} else {
|
||||
try {
|
||||
$dbForProject->deleteDocument(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$documentId
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT);
|
||||
} catch (RestrictedException) {
|
||||
throw new Exception(Exception::DOCUMENT_DELETE_RESTRICTED);
|
||||
}
|
||||
}
|
||||
|
||||
$operations = 0;
|
||||
@@ -5160,21 +5378,23 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $operations))
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations));
|
||||
|
||||
$relationships = \array_map(
|
||||
fn ($document) => $document->getAttribute('key'),
|
||||
\array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
)
|
||||
);
|
||||
if (!empty($transactionId)) {
|
||||
$relationships = \array_map(
|
||||
fn ($document) => $document->getAttribute('key'),
|
||||
\array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
)
|
||||
);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('documentId', $document->getId())
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $database)
|
||||
->setPayload($response->output($document, Response::MODEL_DOCUMENT), sensitive: $relationships);
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('documentId', $document->getId())
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $database)
|
||||
->setPayload($response->output($document, Response::MODEL_DOCUMENT), sensitive: $relationships);
|
||||
}
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
@@ -5206,12 +5426,13 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->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, new UID(), 'Transaction ID for staging the operation.', true)
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('plan')
|
||||
->action(function (string $databaseId, string $collectionId, array $queries, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan) {
|
||||
->action(function (string $databaseId, string $collectionId, array $queries, ?string $transactionId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan) {
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
@@ -5231,6 +5452,16 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk delete is not supported for collections with relationship attributes');
|
||||
}
|
||||
|
||||
if (!empty($transactionId)) {
|
||||
$transaction = $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
if ($transaction->getAttribute('status') !== 'pending') {
|
||||
throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -5239,20 +5470,32 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
|
||||
$documents = [];
|
||||
|
||||
try {
|
||||
$modified = $dbForProject->deleteDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$queries,
|
||||
onNext: function (Document $document) use ($plan, &$documents) {
|
||||
if (\count($documents) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) {
|
||||
$documents[] = $document;
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT);
|
||||
} catch (RestrictedException) {
|
||||
throw new Exception(Exception::DOCUMENT_DELETE_RESTRICTED);
|
||||
if (!empty($transactionId)) {
|
||||
$dbForProject->createDocument('transactionLog', new Document([
|
||||
'databaseInternalId' => $database->getSequence(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'transactionInternalId' => $transaction->getSequence(),
|
||||
'action' => 'delete',
|
||||
'data' => [$queries],
|
||||
]));
|
||||
|
||||
$modified = 0;
|
||||
} else {
|
||||
try {
|
||||
$modified = $dbForProject->deleteDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$queries,
|
||||
onNext: function (Document $document) use ($plan, &$documents) {
|
||||
if (\count($documents) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) {
|
||||
$documents[] = $document;
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT);
|
||||
} catch (RestrictedException) {
|
||||
throw new Exception(Exception::DOCUMENT_DELETE_RESTRICTED);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($documents as $document) {
|
||||
|
||||
Reference in New Issue
Block a user