Add missing state handlers

This commit is contained in:
Jake Barnby
2025-10-07 22:17:59 +13:00
parent 9e3c439c51
commit eb7306a4fa
2 changed files with 228 additions and 0 deletions
+105
View File
@@ -423,6 +423,33 @@ class TransactionState
];
break;
case 'increment':
case 'decrement':
$attribute = $data['attribute'] ?? null;
$value = $data['value'] ?? 1;
if ($attribute) {
if (isset($state[$collectionId][$documentId])) {
$existingDocument = $state[$collectionId][$documentId]['document'];
$currentValue = $existingDocument->getAttribute($attribute, 0);
$newValue = $action === 'increment' ? $currentValue + $value : $currentValue - $value;
$existingDocument->setAttribute($attribute, $newValue);
$currentAction = $state[$collectionId][$documentId]['action'];
if ($currentAction !== 'create' && $currentAction !== 'upsert') {
$state[$collectionId][$documentId]['action'] = 'update';
}
} else {
$newValue = $action === 'increment' ? $value : -$value;
$state[$collectionId][$documentId] = [
'action' => 'update',
'document' => new Document([$attribute => $newValue]),
'exists' => true
];
}
}
break;
case 'bulkCreate':
if (\is_array($data)) {
foreach ($data as $doc) {
@@ -437,6 +464,84 @@ class TransactionState
}
}
break;
case 'bulkUpdate':
if (isset($data['queries']) && isset($data['data'])) {
$queries = Query::parseQueries($data['queries'] ?? []);
$updateData = $data['data'];
foreach ($state[$collectionId] ?? [] as $docId => $entry) {
if (!$entry['exists']) {
continue;
}
$document = $entry['document'];
$filters = $this->extractFilters($queries);
if ($this->documentMatchesFilters($document, $filters)) {
foreach ($updateData as $key => $value) {
if ($key !== '$id') {
$document->setAttribute($key, $value);
}
}
$currentAction = $state[$collectionId][$docId]['action'];
if ($currentAction !== 'create' && $currentAction !== 'upsert') {
$state[$collectionId][$docId]['action'] = 'update';
}
}
}
}
break;
case 'bulkUpsert':
if (\is_array($data)) {
foreach ($data as $doc) {
if ($doc instanceof Document) {
$doc = $doc->getArrayCopy();
}
$docId = $doc['$id'] ?? null;
if (!$docId) {
continue;
}
if (isset($state[$collectionId][$docId])) {
$existingDocument = $state[$collectionId][$docId]['document'];
foreach ($doc as $key => $value) {
$existingDocument->setAttribute($key, $value);
}
} else {
$state[$collectionId][$docId] = [
'action' => 'upsert',
'document' => new Document($doc),
'exists' => true
];
}
}
}
break;
case 'bulkDelete':
if (isset($data['queries'])) {
$queries = Query::parseQueries($data['queries'] ?? []);
$filters = $this->extractFilters($queries);
foreach ($state[$collectionId] ?? [] as $docId => $entry) {
if (!$entry['exists']) {
continue;
}
$document = $entry['document'];
if ($this->documentMatchesFilters($document, $filters)) {
$state[$collectionId][$docId] = [
'action' => 'delete',
'exists' => false
];
}
}
}
break;
}
}
@@ -4613,4 +4613,127 @@ trait TransactionsBase
$this->assertEquals(404, $response['headers']['status-code']);
}
/**
* Test that bulkUpdate can match documents created in the same transaction
* This tests the fix for the bug where applyBulkUpdateToState was treating
* state entries as Documents instead of arrays with 'document' keys
*/
public function testBulkUpdateMatchesCreatedDocsInSameTransaction(): void
{
$database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'databaseId' => ID::unique(),
'name' => 'BulkUpdateStateDB'
]);
$databaseId = $database['body']['$id'];
$table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'tableId' => ID::unique(),
'name' => 'TestTable',
'permissions' => [
Permission::read(Role::any()),
Permission::create(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
]);
$tableId = $table['body']['$id'];
$this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'status',
'size' => 256,
'required' => true,
]);
$this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'flag',
'size' => 256,
'required' => false,
]);
sleep(3);
$transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$transactionId = $transaction['body']['$id'];
// Create 3 documents with status='pending' in transaction
$docIds = [];
for ($i = 1; $i <= 3; $i++) {
$response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'rowId' => 'test_' . $i,
'data' => [
'status' => 'pending'
],
'transactionId' => $transactionId
]);
$this->assertEquals(201, $response['headers']['status-code']);
$docIds[] = $response['body']['$id'];
}
// Bulk update all documents with status='pending' to add flag='processed'
// This should match all 3 documents created above in the same transaction
$response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'data' => [
'flag' => 'processed'
],
'queries' => [Query::equal('status', ['pending'])->toString()],
'transactionId' => $transactionId
]);
$this->assertEquals(200, $response['headers']['status-code']);
// Commit transaction
$response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'commit' => true
]);
$this->assertEquals(200, $response['headers']['status-code']);
// Verify all 3 documents have the flag set
foreach ($docIds as $docId) {
$response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/{$docId}", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('pending', $response['body']['status']);
$this->assertEquals('processed', $response['body']['flag'], 'Bulk update should have matched document created in same transaction');
}
}
}