2025-09-03 15:57:03 +00:00
< ? php
2026-02-25 10:38:40 +00:00
namespace Tests\E2E\Services\Databases\Transactions ;
2025-09-03 15:57:03 +00:00
use Tests\E2E\Client ;
2026-03-02 06:55:40 +00:00
use Tests\E2E\Scopes\SchemaPolling ;
2025-09-03 15:57:03 +00:00
use Utopia\Database\Helpers\ID ;
use Utopia\Database\Helpers\Permission ;
use Utopia\Database\Helpers\Role ;
2025-11-14 04:31:17 +00:00
use Utopia\Database\Operator ;
2025-09-03 15:57:03 +00:00
use Utopia\Database\Query ;
2025-09-16 01:25:15 +00:00
trait TransactionsBase
2025-09-03 15:57:03 +00:00
{
2026-03-02 06:55:40 +00:00
use SchemaPolling ;
2026-02-25 10:38:40 +00:00
protected static string $sharedDatabaseId = '' ;
protected static string $sharedCollectionId = '' ;
protected static bool $sharedSetupDone = false ;
/**
* Get or create a shared database for tests that don ' t need isolation
*/
protected function getSharedDatabase () : string
{
if ( ! empty ( self :: $sharedDatabaseId )) {
return self :: $sharedDatabaseId ;
}
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'SharedTransactionTestDB'
]);
$this -> assertEquals ( 201 , $database [ 'headers' ][ 'status-code' ]);
self :: $sharedDatabaseId = $database [ 'body' ][ '$id' ];
return self :: $sharedDatabaseId ;
}
/**
* Get or create a shared collection with a 'name' attribute for tests
*/
protected function getSharedCollection () : string
{
if ( ! empty ( self :: $sharedCollectionId )) {
return self :: $sharedCollectionId ;
}
$databaseId = $this -> getSharedDatabase ();
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
$this -> getContainerIdParam () => ID :: unique (),
'name' => 'SharedTestCollection' ,
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$this -> assertEquals ( 201 , $collection [ 'headers' ][ 'status-code' ]);
self :: $sharedCollectionId = $collection [ 'body' ][ '$id' ];
2026-03-19 15:00:42 +00:00
// Create a standard 'name' attribute only if attributes are supported
if ( $this -> getSupportForAttributes ()) {
$nameAttr = $this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , self :: $sharedCollectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
$this -> assertEquals ( 202 , $nameAttr [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
2026-03-19 15:00:42 +00:00
$this -> waitForAllAttributes ( $databaseId , self :: $sharedCollectionId );
}
2026-02-25 10:38:40 +00:00
return self :: $sharedCollectionId ;
}
/**
* Reset shared state after all tests
*/
public static function tearDownAfterClass () : void
{
self :: $sharedDatabaseId = '' ;
self :: $sharedCollectionId = '' ;
self :: $sharedSetupDone = false ;
parent :: tearDownAfterClass ();
}
2025-09-03 15:57:03 +00:00
/**
* Test creating a transaction
*/
2025-09-05 14:37:17 +00:00
public function testCreate () : void
2025-09-03 15:57:03 +00:00
{
// Create database first
2026-02-25 10:38:40 +00:00
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'TransactionTestDatabase'
]);
$this -> assertEquals ( 201 , $database [ 'headers' ][ 'status-code' ]);
$databaseId = $database [ 'body' ][ '$id' ];
// Test creating a transaction with default TTL
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-03 15:57:03 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'body' ]);
$this -> assertArrayHasKey ( 'status' , $response [ 'body' ]);
$this -> assertArrayHasKey ( 'operations' , $response [ 'body' ]);
$this -> assertArrayHasKey ( 'expiresAt' , $response [ 'body' ]);
$this -> assertEquals ( 'pending' , $response [ 'body' ][ 'status' ]);
$this -> assertEquals ( 0 , $response [ 'body' ][ 'operations' ]);
$transactionId1 = $response [ 'body' ][ '$id' ];
// Test creating a transaction with custom TTL
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()), [
2025-09-03 15:57:03 +00:00
'ttl' => 900
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'pending' , $response [ 'body' ][ 'status' ]);
$expiresAt = new \DateTime ( $response [ 'body' ][ 'expiresAt' ]);
$now = new \DateTime ();
$diff = $expiresAt -> getTimestamp () - $now -> getTimestamp ();
$this -> assertGreaterThan ( 800 , $diff );
$this -> assertLessThan ( 1000 , $diff );
$transactionId2 = $response [ 'body' ][ '$id' ];
// Test invalid TTL values
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()), [
2025-09-03 15:57:03 +00:00
'ttl' => 30 // Below minimum
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()), [
2025-09-03 15:57:03 +00:00
'ttl' => 4000 // Above maximum
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
}
/**
2025-09-05 14:37:17 +00:00
* Test adding operations to a transaction
2025-09-03 15:57:03 +00:00
*/
2025-09-12 09:47:40 +00:00
public function testCreateOperations () : void
2025-09-03 15:57:03 +00:00
{
2025-09-05 14:37:17 +00:00
// Create database first
2026-02-25 10:38:40 +00:00
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'TransactionOperationsTestDB'
]);
$this -> assertEquals ( 201 , $database [ 'headers' ][ 'status-code' ]);
$databaseId = $database [ 'body' ][ '$id' ];
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-05 14:37:17 +00:00
$this -> assertEquals ( 201 , $transaction [ 'headers' ][ 'status-code' ]);
$transactionId = $transaction [ 'body' ][ '$id' ];
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
// Create a collection for testing
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'name' => 'TransactionOperationsTest' ,
2026-02-25 10:38:40 +00:00
$this -> getSecurityParam () => false ,
2025-09-03 15:57:03 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 201 , $collection [ 'headers' ][ 'status-code' ]);
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
// Add attributes
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$attribute = $this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , 'string' , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2025-09-03 15:57:03 +00:00
2026-03-19 15:00:42 +00:00
$this -> assertEquals ( 202 , $attribute [ 'headers' ][ 'status-code' ]);
2025-09-03 15:57:03 +00:00
2026-03-19 15:00:42 +00:00
// Wait for attribute to be created
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-03 15:57:03 +00:00
// Add valid operations
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'create' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'doc1' ,
2025-09-03 15:57:03 +00:00
'data' => [
'name' => 'Test Document 1'
]
],
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'create' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'doc2' ,
2025-09-03 15:57:03 +00:00
'data' => [
'name' => 'Test Document 2'
]
]
]
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 2 , $response [ 'body' ][ 'operations' ]);
// Test adding more operations
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'update' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'doc1' ,
2025-09-03 15:57:03 +00:00
'data' => [
'name' => 'Updated Document 1'
]
]
]
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 3 , $response [ 'body' ][ 'operations' ]);
// Test invalid database ID
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => 'invalid_database' ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'create' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'data' => [ 'name' => 'Test' ]
]
]
]);
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ], 'Invalid database should return 404. Got: ' . json_encode ( $response [ 'body' ]));
2026-02-25 10:38:40 +00:00
// Test invalid collection ID
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => 'invalid_collection' ,
2025-09-03 15:57:03 +00:00
'action' => 'create' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'data' => [ 'name' => 'Test' ]
]
]
]);
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
}
/**
2025-09-05 14:37:17 +00:00
* Test committing a transaction
2025-09-03 15:57:03 +00:00
*/
2025-09-05 14:37:17 +00:00
public function testCommit () : void
2025-09-03 15:57:03 +00:00
{
2025-09-05 14:37:17 +00:00
// Create database first
2026-02-25 10:38:40 +00:00
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'TransactionCommitTestDB'
]);
$this -> assertEquals ( 201 , $database [ 'headers' ][ 'status-code' ]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Create collection
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-05 14:37:17 +00:00
'name' => 'TransactionCommitTest' ,
2026-02-25 10:38:40 +00:00
$this -> getSecurityParam () => false ,
2025-09-05 14:37:17 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 201 , $collection [ 'headers' ][ 'status-code' ]);
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-05 14:37:17 +00:00
2026-02-25 10:38:40 +00:00
// Add attributes
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$attribute = $this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , 'string' , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2025-09-05 14:37:17 +00:00
2026-03-19 15:00:42 +00:00
$this -> assertEquals ( 202 , $attribute [ 'headers' ][ 'status-code' ]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-05 14:37:17 +00:00
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-05 14:37:17 +00:00
$this -> assertEquals ( 201 , $transaction [ 'headers' ][ 'status-code' ]);
$transactionId = $transaction [ 'body' ][ '$id' ];
// Add operations
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-05 14:37:17 +00:00
'action' => 'create' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'doc1' ,
2025-09-05 14:37:17 +00:00
'data' => [
'name' => 'Test Document 1'
]
],
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-05 14:37:17 +00:00
'action' => 'create' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'doc2' ,
2025-09-05 14:37:17 +00:00
'data' => [
'name' => 'Test Document 2'
]
],
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-05 14:37:17 +00:00
'action' => 'update' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'doc1' ,
2025-09-05 14:37:17 +00:00
'data' => [
'name' => 'Updated Document 1'
]
]
]
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 3 , $response [ 'body' ][ 'operations' ]);
2025-09-03 15:57:03 +00:00
// Commit the transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-03 15:57:03 +00:00
'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' ]);
$this -> assertEquals ( 'committed' , $response [ 'body' ][ 'status' ]);
2026-02-25 10:38:40 +00:00
// Verify documents were created
$documents = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $documents [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 2 , $documents [ 'body' ][ 'total' ]);
2025-09-03 15:57:03 +00:00
// Verify the update was applied
$doc1Found = false ;
2026-02-25 10:38:40 +00:00
foreach ( $documents [ 'body' ][ $this -> getRecordResource ()] as $doc ) {
2025-09-03 15:57:03 +00:00
if ( $doc [ '$id' ] === 'doc1' ) {
$this -> assertEquals ( 'Updated Document 1' , $doc [ 'name' ]);
$doc1Found = true ;
}
}
$this -> assertTrue ( $doc1Found , 'Document doc1 should exist with updated name' );
// Test committing already committed transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
}
/**
2025-09-05 14:37:17 +00:00
* Test rolling back a transaction
2025-09-03 15:57:03 +00:00
*/
2025-09-05 14:37:17 +00:00
public function testRollback () : void
2025-09-03 15:57:03 +00:00
{
2025-09-05 14:37:17 +00:00
// Create database first
2026-02-25 10:38:40 +00:00
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'TransactionRollbackTestDB'
]);
$this -> assertEquals ( 201 , $database [ 'headers' ][ 'status-code' ]);
$databaseId = $database [ 'body' ][ '$id' ];
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-05 14:37:17 +00:00
$this -> assertEquals ( 201 , $transaction [ 'headers' ][ 'status-code' ]);
$transactionId = $transaction [ 'body' ][ '$id' ];
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
// Create a collection for rollback test
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'name' => 'TransactionRollbackTest' ,
2026-02-25 10:38:40 +00:00
$this -> getSecurityParam () => false ,
2025-09-03 15:57:03 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
// Add attribute
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , 'string' , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'value' ,
'size' => 256 ,
'required' => true ,
]);
2025-09-03 15:57:03 +00:00
2026-03-19 15:00:42 +00:00
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-03 15:57:03 +00:00
// Add operations
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'create' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'rollback_doc' ,
2025-09-03 15:57:03 +00:00
'data' => [
'value' => 'Should not exist'
]
]
]
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
// Rollback the transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'rollback' => true
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2025-09-11 13:45:16 +00:00
$this -> assertEquals ( 'failed' , $response [ 'body' ][ 'status' ]);
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
// Verify no documents were created
$documents = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $documents [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 0 , $documents [ 'body' ][ 'total' ]);
2025-09-03 15:57:03 +00:00
}
/**
* Test transaction expiration
*/
public function testTransactionExpiration () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'ExpirationTestDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
// Create attribute
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'data' ,
'size' => 256 ,
'required' => false ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-03 15:57:03 +00:00
// Create transaction with minimum TTL (60 seconds)
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()), [
2025-09-03 15:57:03 +00:00
'ttl' => 60
]);
$this -> assertEquals ( 201 , $transaction [ 'headers' ][ 'status-code' ]);
$transactionId = $transaction [ 'body' ][ '$id' ];
// Add operation
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'create' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'data' => [ 'data' => 'Should expire' ]
]
]
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
// Verify transaction was created with correct expiration
2026-02-25 10:38:40 +00:00
$txnDetails = $this -> client -> call ( Client :: METHOD_GET , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]));
$this -> assertEquals ( 200 , $txnDetails [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'pending' , $txnDetails [ 'body' ][ 'status' ]);
// Verify expiration time is approximately 60 seconds from now
$expiresAt = new \DateTime ( $txnDetails [ 'body' ][ 'expiresAt' ]);
$now = new \DateTime ();
$diff = $expiresAt -> getTimestamp () - $now -> getTimestamp ();
$this -> assertGreaterThan ( 55 , $diff );
$this -> assertLessThan ( 65 , $diff );
}
/**
* Test maximum operations per transaction
*/
public function testTransactionSizeLimit () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'SizeLimitTestDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'name' => 'TestCollection' ,
'permissions' => [ Permission :: create ( Role :: any ())],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
// Create attribute
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'value' ,
'size' => 256 ,
'required' => false ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-03 15:57:03 +00:00
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-03 15:57:03 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
// Try to add operations exceeding the limit (assuming limit is 100)
// We'll add 50 operations twice to test incremental limit
$operations = [];
for ( $i = 0 ; $i < 50 ; $i ++ ) {
$operations [] = [
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'create' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'doc_' . $i ,
2025-09-03 15:57:03 +00:00
'data' => [ 'value' => 'Test ' . $i ]
];
}
// First batch should succeed
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => $operations
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 50 , $response [ 'body' ][ 'operations' ]);
// Second batch of 50 more operations
$operations = [];
for ( $i = 50 ; $i < 100 ; $i ++ ) {
$operations [] = [
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
$this -> getRecordIdParam () => 'doc_' . $i ,
2025-09-03 15:57:03 +00:00
'action' => 'create' ,
'data' => [ 'value' => 'Test ' . $i ]
];
}
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => $operations
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 100 , $response [ 'body' ][ 'operations' ]);
// Try to add one more operation - should fail
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'create' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'doc_overflow' ,
2025-09-03 15:57:03 +00:00
'data' => [ 'value' => 'This should fail' ]
]
]
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
}
/**
* Test concurrent transactions with conflicting operations
*/
public function testConcurrentTransactionConflicts () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'ConflictTestDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
// Create attribute
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$counterAttr = $this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " integer " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'counter' ,
'required' => true ,
'min' => 0 ,
'max' => 1000000 ,
]);
$this -> assertEquals ( 202 , $counterAttr [ 'headers' ][ 'status-code' ]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
// Create initial document
$doc = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'shared_doc' ,
2025-09-03 15:57:03 +00:00
'data' => [ 'counter' => 100 ]
]);
$this -> assertEquals ( 201 , $doc [ 'headers' ][ 'status-code' ]);
// Create two transactions
2026-02-25 10:38:40 +00:00
$txn1 = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
$txn2 = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-03 15:57:03 +00:00
$transactionId1 = $txn1 [ 'body' ][ '$id' ];
$transactionId2 = $txn2 [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Both transactions try to update the same document
$this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId1 ) . " /operations " , array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'update' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'shared_doc' ,
2025-09-03 15:57:03 +00:00
'data' => [ 'counter' => 200 ]
]
]
]);
2026-02-25 10:38:40 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId2 ) . " /operations " , array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'update' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'shared_doc' ,
2025-09-03 15:57:03 +00:00
'data' => [ 'counter' => 300 ]
]
]
]);
// Commit first transaction
2026-02-25 10:38:40 +00:00
$response1 = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId1 ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
$this -> assertEquals ( 200 , $response1 [ 'headers' ][ 'status-code' ]);
// Commit second transaction - should fail with conflict
2026-02-25 10:38:40 +00:00
$response2 = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId2 ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
2026-02-25 10:38:40 +00:00
// MongoDB adapter doesn't map write conflicts to ConflictException, so it returns 500 instead of 409
$this -> assertContains ( $response2 [ 'headers' ][ 'status-code' ], [ 409 , 500 ], 'Expected 409 (conflict) or 500 (MongoDB adapter limitation)' );
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
// Verify the document has the value from first transaction
$doc = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " shared_doc " ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $doc [ 'body' ][ 'counter' ]);
}
/**
2026-02-25 10:38:40 +00:00
* Test deleting a document that ' s being updated in a transaction
2025-09-03 15:57:03 +00:00
*/
public function testDeleteDocumentDuringTransaction () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'DeleteConflictDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
// Create attribute
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'data' ,
'size' => 256 ,
'required' => false ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
// Create document
$doc = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'target_doc' ,
2025-09-03 15:57:03 +00:00
'data' => [ 'data' => 'Original' ]
]);
$this -> assertEquals ( 201 , $doc [ 'headers' ][ 'status-code' ]);
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-03 15:57:03 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
// Add update operation to transaction
2026-02-25 10:38:40 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'update' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'target_doc' ,
2025-09-03 15:57:03 +00:00
'data' => [ 'data' => 'Updated in transaction' ]
]
]
]);
2026-02-25 10:38:40 +00:00
// Delete the document outside of transaction
$response = $this -> client -> call ( Client :: METHOD_DELETE , $this -> getRecordUrl ( $databaseId , $collectionId , " target_doc " ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]));
$this -> assertEquals ( 204 , $response [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
// Try to commit transaction - should fail because document no longer exists
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]); // Conflict
2025-09-03 15:57:03 +00:00
}
/**
* Test bulk operations in transactions
*/
public function testBulkOperations () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'BulkOpsDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
// Create attributes
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2025-09-03 15:57:03 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'category' ,
'size' => 256 ,
'required' => true ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
// Create some initial documents
2025-09-03 15:57:03 +00:00
for ( $i = 1 ; $i <= 5 ; $i ++ ) {
2026-02-25 10:38:40 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'existing_' . $i ,
2025-09-03 15:57:03 +00:00
'data' => [
'name' => 'Existing ' . $i ,
'category' => 'old'
]
]);
}
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-03 15:57:03 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
// Add bulk operations
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
// Bulk create
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'bulkCreate' ,
'data' => [
[ '$id' => 'bulk_1' , 'name' => 'Bulk 1' , 'category' => 'new' ],
[ '$id' => 'bulk_2' , 'name' => 'Bulk 2' , 'category' => 'new' ],
[ '$id' => 'bulk_3' , 'name' => 'Bulk 3' , 'category' => 'new' ],
]
],
// Bulk update
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'bulkUpdate' ,
'data' => [
'queries' => [ Query :: equal ( 'category' , [ 'old' ]) -> toString ()],
'data' => [ 'category' => 'updated' ]
]
],
// Bulk delete
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'bulkDelete' ,
'data' => [
'queries' => [ Query :: equal ( 'name' , [ 'Existing 5' ]) -> toString ()]
]
]
]
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-03 15:57:03 +00:00
'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 results
2026-02-25 10:38:40 +00:00
$documents = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2026-02-25 10:38:40 +00:00
// Should have 7 documents (5 existing - 1 deleted + 3 new)
$this -> assertEquals ( 7 , $documents [ 'body' ][ 'total' ]);
2025-09-03 15:57:03 +00:00
// Check categories were updated
$oldCategoryCount = 0 ;
$updatedCategoryCount = 0 ;
$newCategoryCount = 0 ;
2026-02-25 10:38:40 +00:00
foreach ( $documents [ 'body' ][ $this -> getRecordResource ()] as $doc ) {
2025-09-03 15:57:03 +00:00
switch ( $doc [ 'category' ]) {
case 'old' :
$oldCategoryCount ++ ;
break ;
case 'updated' :
$updatedCategoryCount ++ ;
break ;
case 'new' :
$newCategoryCount ++ ;
break ;
}
}
$this -> assertEquals ( 0 , $oldCategoryCount );
$this -> assertEquals ( 4 , $updatedCategoryCount ); // 4 existing docs updated
$this -> assertEquals ( 3 , $newCategoryCount ); // 3 new docs
}
/**
* Test transaction with mixed success and failure operations
*/
public function testPartialFailureRollback () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'PartialFailureDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
// Create attributes with constraints
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'email' ,
'size' => 256 ,
'required' => true ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-03 15:57:03 +00:00
2025-09-04 13:02:30 +00:00
2025-09-03 15:57:03 +00:00
// Create unique index on email
2026-02-25 10:38:40 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getIndexUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'unique_email' ,
'type' => 'unique' ,
2026-02-25 10:38:40 +00:00
$this -> getIndexAttributesParam () => [ 'email' ],
2025-09-03 15:57:03 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> waitForIndex ( $databaseId , $collectionId , 'unique_email' );
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
// Create an existing document
$this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'data' => [ 'email' => 'existing@example.com' ]
]);
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-03 15:57:03 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
// Add operations - mix of valid and invalid
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'create' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'data' => [ 'email' => 'valid1@example.com' ] // Valid
],
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'create' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'data' => [ 'email' => 'valid2@example.com' ] // Valid
],
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'create' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'data' => [ 'email' => 'existing@example.com' ] // Will fail - duplicate
],
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'create' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'data' => [ 'email' => 'valid3@example.com' ] // Would be valid but should rollback
],
]
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
// Try to commit - should fail and rollback all operations
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
$this -> assertEquals ( 409 , $response [ 'headers' ][ 'status-code' ]); // Conflict due to duplicate
2026-02-25 10:38:40 +00:00
// Verify NO new documents were created (atomicity)
$documents = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 1 , $documents [ 'body' ][ 'total' ]); // Only the original document
$this -> assertEquals ( 'existing@example.com' , $documents [ 'body' ][ $this -> getRecordResource ()][ 0 ][ 'email' ]);
2025-09-03 15:57:03 +00:00
}
/**
* Test double commit / rollback attempts
*/
public function testDoubleCommitRollback () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'DoubleCommitDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'name' => 'TestCollection' ,
'permissions' => [ Permission :: create ( Role :: any ())],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-03 15:57:03 +00:00
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
// Create attribute
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'data' ,
'size' => 256 ,
'required' => false ,
]);
2025-09-03 15:57:03 +00:00
2026-03-19 15:00:42 +00:00
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-03 15:57:03 +00:00
// Test double commit
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-03 15:57:03 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
// Add operation
2026-02-25 10:38:40 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'create' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'data' => [ 'data' => 'Test' ]
]
]
]);
// First commit
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-03 15:57:03 +00:00
'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' ]);
// Second commit attempt - should fail
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]); // Bad request - already committed
// Test double rollback
2026-02-25 10:38:40 +00:00
$transaction2 = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-03 15:57:03 +00:00
$transactionId2 = $transaction2 [ 'body' ][ '$id' ];
// First rollback
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId2 ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'rollback' => true
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Second rollback attempt - should fail
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId2 ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'rollback' => true
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]); // Bad request - already rolled back
}
/**
2026-02-25 10:38:40 +00:00
* Test operations on non - existent documents
2025-09-03 15:57:03 +00:00
*/
public function testOperationsOnNonExistentDocuments () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'NonExistentDocDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-03 15:57:03 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-03 15:57:03 +00:00
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
// Create attribute
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'data' ,
'size' => 256 ,
'required' => false ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-03 15:57:03 +00:00
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-03 15:57:03 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Try to update non-existent document - should fail at staging time with early validation
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'update' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'non_existent_doc' ,
2025-09-03 15:57:03 +00:00
'data' => [ 'data' => 'Should fail' ]
]
]
]);
2025-10-04 08:10:50 +00:00
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]); // Document not found at staging time
2025-09-03 15:57:03 +00:00
2026-02-25 10:38:40 +00:00
// Test delete non-existent document - should also fail at staging time with early validation
$transaction2 = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-03 15:57:03 +00:00
$transactionId2 = $transaction2 [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId2 ) . " /operations " , array_merge ([
2025-09-03 15:57:03 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-03 15:57:03 +00:00
'action' => 'delete' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'non_existent_doc' ,
2025-09-03 15:57:03 +00:00
'data' => []
]
]
]);
2025-10-04 08:10:50 +00:00
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]); // Document not found at staging time
2025-09-03 15:57:03 +00:00
}
2025-09-05 14:37:17 +00:00
/**
* Test createDocument with transactionId via normal route
*/
public function testCreateDocument () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'WriteRoutesTestDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-05 14:37:17 +00:00
'name' => 'TestCollection' ,
2026-02-25 10:38:40 +00:00
$this -> getSecurityParam () => false ,
2025-09-05 14:37:17 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-05 14:37:17 +00:00
2026-02-25 10:38:40 +00:00
// Create attributes
$attributes = [
2025-09-05 14:37:17 +00:00
[ 'key' => 'name' , 'type' => 'string' , 'size' => 256 , 'required' => true ],
[ 'key' => 'counter' , 'type' => 'integer' , 'required' => false , 'min' => 0 , 'max' => 10000 ],
[ 'key' => 'category' , 'type' => 'string' , 'size' => 256 , 'required' => false ],
[ 'key' => 'data' , 'type' => 'string' , 'size' => 256 , 'required' => false ],
];
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
foreach ( $attributes as $attr ) {
$type = $attr [ 'type' ];
unset ( $attr [ 'type' ]);
2025-09-05 16:26:49 +00:00
2026-03-19 15:00:42 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , $type , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), $attr );
2025-09-05 14:37:17 +00:00
2026-03-19 15:00:42 +00:00
$this -> assertEquals ( 202 , $response [ 'headers' ][ 'status-code' ]);
}
$this -> waitForAllAttributes ( $databaseId , $collectionId );
2025-09-05 14:37:17 +00:00
}
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-05 14:37:17 +00:00
$this -> assertEquals ( 201 , $transaction [ 'headers' ][ 'status-code' ]);
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Create document via normal route with transactionId
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'doc_from_route' ,
2025-09-05 14:37:17 +00:00
'data' => [
'name' => 'Created via normal route' ,
'counter' => 100 ,
'category' => 'test'
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
// Document should not exist outside transaction yet
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_from_route " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-05 14:37:17 +00:00
'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' ]);
// Document should now exist
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_from_route " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'Created via normal route' , $response [ 'body' ][ 'name' ]);
}
/**
* Test updateDocument with transactionId via normal route
*/
public function testUpdateDocument () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'UpdateRouteTestDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-05 14:37:17 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-05 14:37:17 +00:00
2026-02-25 10:38:40 +00:00
// Create attributes
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2025-09-05 14:37:17 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " integer " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'counter' ,
'required' => false ,
'min' => 0 ,
'max' => 10000 ,
]);
2025-09-05 14:37:17 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'category' ,
'size' => 256 ,
'required' => false ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-05 14:37:17 +00:00
2026-02-25 10:38:40 +00:00
// Create document outside transaction
$doc = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'doc_to_update' ,
2025-09-05 14:37:17 +00:00
'data' => [
'name' => 'Original name' ,
'counter' => 50 ,
'category' => 'original'
]
]);
$this -> assertEquals ( 201 , $doc [ 'headers' ][ 'status-code' ]);
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-05 14:37:17 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Update document via normal route with transactionId
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_to_update " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
'name' => 'Updated via normal route' ,
'counter' => 150 ,
'category' => 'updated'
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Document should still have original values outside transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_to_update " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 'Original name' , $response [ 'body' ][ 'name' ]);
$this -> assertEquals ( 50 , $response [ 'body' ][ 'counter' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-05 14:37:17 +00:00
'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' ]);
// Document should now have updated values
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_to_update " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 'Updated via normal route' , $response [ 'body' ][ 'name' ]);
$this -> assertEquals ( 150 , $response [ 'body' ][ 'counter' ]);
}
/**
* Test upsertDocument with transactionId via normal route
*/
public function testUpsertDocument () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'UpsertRouteTestDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-05 14:37:17 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-05 14:37:17 +00:00
2026-02-25 10:38:40 +00:00
// Create attributes
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2025-09-05 14:37:17 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " integer " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'counter' ,
'required' => false ,
'min' => 0 ,
'max' => 10000 ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-05 14:37:17 +00:00
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-05 14:37:17 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Upsert document (create) via normal route with transactionId
$response = $this -> client -> call ( Client :: METHOD_PUT , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_upsert " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'doc_upsert' ,
2025-09-05 14:37:17 +00:00
'data' => [
'name' => 'Created by upsert' ,
'counter' => 25
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
// Document should not exist outside transaction yet
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_upsert " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
// Upsert same document (update) in same transaction
$response = $this -> client -> call ( Client :: METHOD_PUT , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_upsert " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'doc_upsert' ,
2025-09-05 14:37:17 +00:00
'data' => [
'name' => 'Updated by upsert' ,
'counter' => 75
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]); // Upsert in transaction returns 201
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
2025-09-05 16:26:49 +00:00
2025-09-05 14:37:17 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Document should now exist with updated values
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_upsert " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'Updated by upsert' , $response [ 'body' ][ 'name' ]);
$this -> assertEquals ( 75 , $response [ 'body' ][ 'counter' ]);
}
/**
* Test deleteDocument with transactionId via normal route
*/
public function testDeleteDocument () : void
{
2026-02-25 10:38:40 +00:00
// Use shared database and collection to avoid overwhelming the worker
$databaseId = $this -> getSharedDatabase ();
$collectionId = $this -> getSharedCollection ();
$docId = ID :: unique ();
2025-09-05 14:37:17 +00:00
2026-02-25 10:38:40 +00:00
// Create document outside transaction
$doc = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => $docId ,
2025-09-05 14:37:17 +00:00
'data' => [ 'name' => 'Will be deleted' ]
]);
$this -> assertEquals ( 201 , $doc [ 'headers' ][ 'status-code' ]);
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-05 14:37:17 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Delete document via normal route with transactionId
$response = $this -> client -> call ( Client :: METHOD_DELETE , $this -> getRecordUrl ( $databaseId , $collectionId , $docId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'transactionId' => $transactionId
]);
$this -> assertEquals ( 204 , $response [ 'headers' ][ 'status-code' ]);
// Document should still exist outside transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , $docId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
2025-09-05 16:26:49 +00:00
2025-09-05 14:37:17 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Document should no longer exist
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_to_delete " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
}
/**
* Test bulkCreate with transactionId via normal route
*/
public function testBulkCreate () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'BulkCreateTestDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-05 14:37:17 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-05 14:37:17 +00:00
2026-02-25 10:38:40 +00:00
// Create attributes
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2026-02-27 11:45:00 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'category' ,
'size' => 256 ,
'required' => false ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2026-02-27 11:45:00 +00:00
// Create transaction
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-05 14:37:17 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
// Bulk create via normal route with transactionId
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordResource () => [
2025-09-05 14:37:17 +00:00
[
'$id' => 'bulk_create_1' ,
'name' => 'Bulk created 1' ,
'category' => 'bulk_created'
],
[
'$id' => 'bulk_create_2' ,
'name' => 'Bulk created 2' ,
'category' => 'bulk_created'
],
[
'$id' => 'bulk_create_3' ,
'name' => 'Bulk created 3' ,
'category' => 'bulk_created'
]
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]); // Bulk operations return 200
// Documents should not exist outside transaction yet
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'queries' => [ Query :: equal ( 'category' , [ 'bulk_created' ]) -> toString ()]
]);
$this -> assertEquals ( 0 , $response [ 'body' ][ 'total' ]);
2026-02-25 10:38:40 +00:00
// Individual document check
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " bulk_create_1 " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
2025-09-05 16:26:49 +00:00
2025-09-05 14:37:17 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Documents should now exist
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'queries' => [ Query :: equal ( 'category' , [ 'bulk_created' ]) -> toString ()]
]);
$this -> assertEquals ( 3 , $response [ 'body' ][ 'total' ]);
2026-02-25 10:38:40 +00:00
// Verify individual documents
2025-09-05 14:37:17 +00:00
for ( $i = 1 ; $i <= 3 ; $i ++ ) {
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " bulk_create_ { $i } " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( " Bulk created { $i } " , $response [ 'body' ][ 'name' ]);
$this -> assertEquals ( 'bulk_created' , $response [ 'body' ][ 'category' ]);
}
}
/**
* Test bulkUpdate with transactionId via normal route
*/
public function testBulkUpdate () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'BulkUpdateTestDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-05 14:37:17 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-05 14:37:17 +00:00
2026-02-25 10:38:40 +00:00
// Create attributes
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2025-09-05 14:37:17 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'category' ,
'size' => 256 ,
'required' => false ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-05 14:37:17 +00:00
2026-02-25 10:38:40 +00:00
// Create documents for bulk testing
2025-09-05 14:37:17 +00:00
for ( $i = 1 ; $i <= 3 ; $i ++ ) {
2026-02-25 10:38:40 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'bulk_update_' . $i ,
2025-09-05 14:37:17 +00:00
'data' => [
'name' => 'Bulk doc ' . $i ,
'category' => 'bulk_test'
]
]);
}
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-05 14:37:17 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
// Bulk update via normal route with transactionId
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'queries' => [ Query :: equal ( 'category' , [ 'bulk_test' ]) -> toString ()],
'data' => [ 'category' => 'bulk_updated' ],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Documents should still have original category outside transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'queries' => [ Query :: equal ( 'category' , [ 'bulk_test' ]) -> toString ()]
]);
$this -> assertEquals ( 3 , $response [ 'body' ][ 'total' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
2025-09-05 16:26:49 +00:00
2025-09-05 14:37:17 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Documents should now have updated category
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'queries' => [ Query :: equal ( 'category' , [ 'bulk_updated' ]) -> toString ()]
]);
$this -> assertEquals ( 3 , $response [ 'body' ][ 'total' ]);
}
/**
* Test bulkUpsert with transactionId via normal route
*/
public function testBulkUpsert () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'BulkUpsertTestDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-05 14:37:17 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-05 14:37:17 +00:00
2026-02-25 10:38:40 +00:00
// Create attributes
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2025-09-05 14:37:17 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " integer " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'counter' ,
'required' => false ,
'min' => 0 ,
'max' => 10000 ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-05 14:37:17 +00:00
2026-02-25 10:38:40 +00:00
// Create one document outside transaction
$this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'bulk_upsert_existing' ,
2025-09-05 14:37:17 +00:00
'data' => [
'name' => 'Existing doc' ,
'counter' => 10
]
]);
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-05 14:37:17 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
// Bulk upsert via normal route with transactionId (updates existing, creates new)
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PUT , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordResource () => [
2025-09-05 14:37:17 +00:00
[
'$id' => 'bulk_upsert_existing' ,
'name' => 'Updated existing' ,
'counter' => 20
],
[
'$id' => 'bulk_upsert_new' ,
'name' => 'New doc' ,
'counter' => 30
]
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
// Original document should be unchanged, new document shouldn't exist outside transaction
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " bulk_upsert_existing " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 'Existing doc' , $response [ 'body' ][ 'name' ]);
$this -> assertEquals ( 10 , $response [ 'body' ][ 'counter' ]);
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " bulk_upsert_new " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
2025-09-05 16:26:49 +00:00
2025-09-05 14:37:17 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
// Check both documents exist with updated values
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " bulk_upsert_existing " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 'Updated existing' , $response [ 'body' ][ 'name' ]);
$this -> assertEquals ( 20 , $response [ 'body' ][ 'counter' ]);
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " bulk_upsert_new " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 'New doc' , $response [ 'body' ][ 'name' ]);
$this -> assertEquals ( 30 , $response [ 'body' ][ 'counter' ]);
}
/**
* Test bulkDelete with transactionId via normal route
*/
public function testBulkDelete () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'BulkDeleteTestDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-05 14:37:17 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-05 14:37:17 +00:00
2026-02-25 10:38:40 +00:00
// Create attributes
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2025-09-05 14:37:17 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'category' ,
'size' => 256 ,
'required' => false ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-05 14:37:17 +00:00
2026-02-25 10:38:40 +00:00
// Create documents for bulk testing
2025-09-05 14:37:17 +00:00
for ( $i = 1 ; $i <= 3 ; $i ++ ) {
2026-02-25 10:38:40 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'bulk_delete_' . $i ,
2025-09-05 14:37:17 +00:00
'data' => [
'name' => 'Delete doc ' . $i ,
'category' => 'bulk_delete_test'
]
]);
}
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-05 14:37:17 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
// Bulk delete via normal route with transactionId
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_DELETE , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'queries' => [ Query :: equal ( 'category' , [ 'bulk_delete_test' ]) -> toString ()],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]); // Bulk delete with transaction returns 200
// Documents should still exist outside transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'queries' => [ Query :: equal ( 'category' , [ 'bulk_delete_test' ]) -> toString ()]
]);
$this -> assertEquals ( 3 , $response [ 'body' ][ 'total' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
2025-09-05 16:26:49 +00:00
2025-09-05 14:37:17 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Documents should now be deleted
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'queries' => [ Query :: equal ( 'category' , [ 'bulk_delete_test' ]) -> toString ()]
]);
$this -> assertEquals ( 0 , $response [ 'body' ][ 'total' ]);
}
/**
* Test multiple single route operations in one transaction
*/
public function testMixedSingleOperations () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'MultipleSingleRoutesDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-05 14:37:17 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-05 14:37:17 +00:00
2026-02-25 10:38:40 +00:00
// Create attributes
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2025-09-05 14:37:17 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'status' ,
'size' => 256 ,
'required' => false ,
]);
2025-09-05 14:37:17 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " integer " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'priority' ,
'required' => false ,
'min' => 1 ,
'max' => 10 ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-05 14:37:17 +00:00
2026-02-25 10:38:40 +00:00
// Create an existing document outside transaction for testing
$existingDoc = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'existing_doc' ,
2025-09-05 14:37:17 +00:00
'data' => [
'name' => 'Existing Document' ,
'status' => 'active' ,
'priority' => 5
]
]);
$this -> assertEquals ( 201 , $existingDoc [ 'headers' ][ 'status-code' ]);
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-05 14:37:17 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
$this -> assertEquals ( 201 , $transaction [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
// 1. Create new document via normal route with transactionId
$response1 = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'new_doc_1' ,
2025-09-05 14:37:17 +00:00
'data' => [
'name' => 'New Document 1' ,
'status' => 'pending' ,
'priority' => 1
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 201 , $response1 [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
// 2. Create another document via normal route with transactionId
$response2 = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'new_doc_2' ,
2025-09-05 14:37:17 +00:00
'data' => [
'name' => 'New Document 2' ,
'status' => 'pending' ,
'priority' => 2
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 201 , $response2 [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
// 3. Update existing document via normal route with transactionId
$response3 = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getRecordUrl ( $databaseId , $collectionId , " existing_doc " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
'status' => 'updated' ,
'priority' => 10
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 200 , $response3 [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
// 4. Update the first new document (created in same transaction)
$response4 = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getRecordUrl ( $databaseId , $collectionId , " new_doc_1 " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
'status' => 'active' ,
'priority' => 8
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 200 , $response4 [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
// 5. Delete the second new document (created in same transaction)
$response5 = $this -> client -> call ( Client :: METHOD_DELETE , $this -> getRecordUrl ( $databaseId , $collectionId , " new_doc_2 " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'transactionId' => $transactionId
]);
$this -> assertEquals ( 204 , $response5 [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
// 6. Upsert a new document via normal route with transactionId
$response6 = $this -> client -> call ( Client :: METHOD_PUT , $this -> getRecordUrl ( $databaseId , $collectionId , " upserted_doc " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'upserted_doc' ,
2025-09-05 14:37:17 +00:00
'data' => [
'name' => 'Upserted Document' ,
'status' => 'new' ,
'priority' => 3
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 201 , $response6 [ 'headers' ][ 'status-code' ]);
// Check transaction has correct number of operations
2026-02-25 10:38:40 +00:00
$txnDetails = $this -> client -> call ( Client :: METHOD_GET , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]));
$this -> assertEquals ( 200 , $txnDetails [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 6 , $txnDetails [ 'body' ][ 'operations' ]); // 6 operations total
// Verify nothing exists outside transaction yet
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " new_doc_1 " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " upserted_doc " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
// Existing doc should still have original values
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " existing_doc " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 'active' , $response [ 'body' ][ 'status' ]);
$this -> assertEquals ( 5 , $response [ 'body' ][ 'priority' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-05 14:37:17 +00:00
'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' ]);
$this -> assertEquals ( 'committed' , $response [ 'body' ][ 'status' ]);
// Verify final state after commit
// new_doc_1 should exist with updated values
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " new_doc_1 " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'New Document 1' , $response [ 'body' ][ 'name' ]);
$this -> assertEquals ( 'active' , $response [ 'body' ][ 'status' ]);
$this -> assertEquals ( 8 , $response [ 'body' ][ 'priority' ]);
// new_doc_2 should not exist (was deleted in transaction)
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " new_doc_2 " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
// existing_doc should have updated values
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " existing_doc " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 'updated' , $response [ 'body' ][ 'status' ]);
$this -> assertEquals ( 10 , $response [ 'body' ][ 'priority' ]);
// upserted_doc should exist
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " upserted_doc " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'Upserted Document' , $response [ 'body' ][ 'name' ]);
$this -> assertEquals ( 'new' , $response [ 'body' ][ 'status' ]);
$this -> assertEquals ( 3 , $response [ 'body' ][ 'priority' ]);
2026-02-25 10:38:40 +00:00
// Verify total document count
$documents = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 3 , $documents [ 'body' ][ 'total' ]); // existing_doc, new_doc_1, upserted_doc
2025-09-05 14:37:17 +00:00
}
/**
* Test mixed operations with transactions
*/
public function testMixedOperations () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'MixedOpsTestDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-05 14:37:17 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-05 14:37:17 +00:00
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
// Create attribute
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-05 14:37:17 +00:00
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-05 14:37:17 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
// Add operation via Operations\Add endpoint
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-05 14:37:17 +00:00
'action' => 'create' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'mixed_doc1' ,
2025-09-05 14:37:17 +00:00
'data' => [ 'name' => 'Via Operations Add' ]
]
]
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 1 , $response [ 'body' ][ 'operations' ]);
// Add operation via normal route with transactionId
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'mixed_doc2' ,
2025-09-05 14:37:17 +00:00
'data' => [ 'name' => 'Via normal route' ],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
// Check transaction now has 2 operations
2026-02-25 10:38:40 +00:00
$txnDetails = $this -> client -> call ( Client :: METHOD_GET , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]));
$this -> assertEquals ( 2 , $txnDetails [ 'body' ][ 'operations' ]);
2026-02-25 10:38:40 +00:00
// Both documents shouldn't exist yet
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " mixed_doc1 " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " mixed_doc2 " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
2025-09-05 16:26:49 +00:00
2025-09-05 14:37:17 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
// Both documents should now exist
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " mixed_doc1 " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'Via Operations Add' , $response [ 'body' ][ 'name' ]);
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " mixed_doc2 " ), array_merge ([
2025-09-05 14:37:17 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'Via normal route' , $response [ 'body' ][ 'name' ]);
}
2025-09-08 07:20:26 +00:00
/**
2026-02-25 10:38:40 +00:00
* Test bulk update with queries that should match documents created in the same transaction
2025-09-08 07:20:26 +00:00
*/
public function testBulkUpdateWithTransactionAwareQueries () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'BulkTxnAwareDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-08 07:20:26 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-08 07:20:26 +00:00
2026-02-25 10:38:40 +00:00
// Create attributes
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2025-09-08 07:20:26 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " integer " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'age' ,
'required' => true ,
]);
2025-09-08 07:20:26 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), 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 -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-08 07:20:26 +00:00
2026-02-25 10:38:40 +00:00
// Create some existing documents
2025-09-08 07:20:26 +00:00
for ( $i = 1 ; $i <= 3 ; $i ++ ) {
2026-02-25 10:38:40 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'existing_' . $i ,
2025-09-08 07:20:26 +00:00
'data' => [
'name' => 'Existing ' . $i ,
'age' => 20 + $i ,
'status' => 'inactive'
]
]);
}
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-08 07:20:26 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Step 1: Create new documents with age > 25 in transaction
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'txn_doc_1' ,
2025-09-08 07:20:26 +00:00
'data' => [
'name' => 'Transaction Doc 1' ,
'age' => 30 ,
'status' => 'inactive'
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'txn_doc_2' ,
2025-09-08 07:20:26 +00:00
'data' => [
'name' => 'Transaction Doc 2' ,
'age' => 35 ,
'status' => 'inactive'
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
// Step 2: Bulk update all documents with age > 25 to have status 'active'
// This should match both existing_3 (age=23 doesn't match, age=24 doesn't match, but existing documents have age 21,22,23)
2025-09-08 07:20:26 +00:00
// Wait, let me fix the ages - existing docs have ages 21, 22, 23, so only txn docs should match
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
'status' => 'active'
],
'queries' => [ Query :: greaterThan ( 'age' , 25 ) -> toString ()],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-08 07:20:26 +00:00
'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' ]);
2026-02-25 10:38:40 +00:00
// Verify that documents created in the transaction were updated by the bulk update
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " txn_doc_1 " ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'active' , $response [ 'body' ][ 'status' ], 'Document created in transaction should be updated by bulk update query' );
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " txn_doc_2 " ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'active' , $response [ 'body' ][ 'status' ], 'Document created in transaction should be updated by bulk update query' );
2026-02-25 10:38:40 +00:00
// Verify existing documents were not affected
2025-09-08 07:20:26 +00:00
for ( $i = 1 ; $i <= 3 ; $i ++ ) {
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " existing_ { $i } " ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 'inactive' , $response [ 'body' ][ 'status' ], " Existing document { $i } should remain inactive (age <= 25) " );
2025-09-08 07:20:26 +00:00
}
}
/**
2026-02-25 10:38:40 +00:00
* Test bulk update with queries that should match documents updated in the same transaction
2025-09-08 07:20:26 +00:00
*/
public function testBulkUpdateMatchingUpdatedDocuments () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'BulkUpdateTxnDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-08 07:20:26 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-08 07:20:26 +00:00
2026-02-25 10:38:40 +00:00
// Create attributes
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2025-09-08 07:20:26 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'category' ,
'size' => 256 ,
'required' => true ,
]);
2025-09-08 07:20:26 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'priority' ,
'size' => 256 ,
'required' => true ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-08 07:20:26 +00:00
2026-02-25 10:38:40 +00:00
// Create existing documents
2025-09-08 07:20:26 +00:00
for ( $i = 1 ; $i <= 4 ; $i ++ ) {
2026-02-25 10:38:40 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'doc_' . $i ,
2025-09-08 07:20:26 +00:00
'data' => [
'name' => 'Document ' . $i ,
'category' => 'normal' ,
'priority' => 'low'
]
]);
}
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-08 07:20:26 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Step 1: Update some documents to have category 'special' in transaction
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_1 " ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
'category' => 'special'
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_2 " ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
'category' => 'special'
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
// Step 2: Bulk update all documents with category 'special' to have priority 'high'
// This should match the documents we just updated in the transaction
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
'priority' => 'high'
],
'queries' => [ Query :: equal ( 'category' , [ 'special' ]) -> toString ()],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-08 07:20:26 +00:00
'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' ]);
2026-02-25 10:38:40 +00:00
// Verify that the updated documents were matched by bulk update
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_1 " ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'special' , $response [ 'body' ][ 'category' ]);
$this -> assertEquals ( 'high' , $response [ 'body' ][ 'priority' ], 'Document updated in transaction should be matched by bulk update query' );
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_2 " ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'special' , $response [ 'body' ][ 'category' ]);
$this -> assertEquals ( 'high' , $response [ 'body' ][ 'priority' ], 'Document updated in transaction should be matched by bulk update query' );
2026-02-25 10:38:40 +00:00
// Verify other documents were not affected
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_3 " ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'normal' , $response [ 'body' ][ 'category' ]);
$this -> assertEquals ( 'low' , $response [ 'body' ][ 'priority' ]);
}
/**
2026-02-25 10:38:40 +00:00
* Test bulk delete with queries that should match documents created in the same transaction
2025-09-08 07:20:26 +00:00
*/
public function testBulkDeleteMatchingCreatedDocuments () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'BulkDeleteTxnDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-08 07:20:26 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-08 07:20:26 +00:00
2026-02-25 10:38:40 +00:00
// Create attributes
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2025-09-08 07:20:26 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'type' ,
'size' => 256 ,
'required' => true ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-08 07:20:26 +00:00
2026-02-25 10:38:40 +00:00
// Create existing documents
2025-09-08 07:20:26 +00:00
for ( $i = 1 ; $i <= 3 ; $i ++ ) {
2026-02-25 10:38:40 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'existing_' . $i ,
2025-09-08 07:20:26 +00:00
'data' => [
'name' => 'Existing ' . $i ,
'type' => 'permanent'
]
]);
}
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-08 07:20:26 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Step 1: Create temporary documents in transaction
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'temp_1' ,
2025-09-08 07:20:26 +00:00
'data' => [
'name' => 'Temporary 1' ,
'type' => 'temporary'
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'temp_2' ,
2025-09-08 07:20:26 +00:00
'data' => [
'name' => 'Temporary 2' ,
'type' => 'temporary'
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
// Step 2: Bulk delete all documents with type 'temporary'
// This should delete the documents we just created in the transaction
$response = $this -> client -> call ( Client :: METHOD_DELETE , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'queries' => [ Query :: equal ( 'type' , [ 'temporary' ]) -> toString ()],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-08 07:20:26 +00:00
'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' ]);
2026-02-25 10:38:40 +00:00
// Verify temporary documents were deleted (should not exist)
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " temp_1 " ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ], 'Temporary document created and deleted in transaction should not exist' );
2025-09-08 07:20:26 +00:00
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " temp_2 " ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ], 'Temporary document created and deleted in transaction should not exist' );
2025-09-08 07:20:26 +00:00
2026-02-25 10:38:40 +00:00
// Verify existing documents were not affected
2025-09-08 07:20:26 +00:00
for ( $i = 1 ; $i <= 3 ; $i ++ ) {
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " existing_ { $i } " ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ], " Permanent document { $i } should still exist " );
2025-09-08 07:20:26 +00:00
$this -> assertEquals ( 'permanent' , $response [ 'body' ][ 'type' ]);
}
}
/**
2026-02-25 10:38:40 +00:00
* Test bulk delete with queries that should match documents updated in the same transaction
2025-09-08 07:20:26 +00:00
*/
public function testBulkDeleteMatchingUpdatedDocuments () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'BulkDeleteUpdateTxnDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-09-08 07:20:26 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: read ( Role :: any ()),
2026-03-19 15:00:42 +00:00
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
2026-03-19 13:48:27 +00:00
]);
2025-09-08 07:20:26 +00:00
2026-03-19 15:00:42 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
// Create attributes
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), 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 -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-08 07:20:26 +00:00
2026-02-25 10:38:40 +00:00
// Create existing documents
2025-09-08 07:20:26 +00:00
for ( $i = 1 ; $i <= 5 ; $i ++ ) {
2026-02-25 10:38:40 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'doc_' . $i ,
2025-09-08 07:20:26 +00:00
'data' => [
'name' => 'Document ' . $i ,
'status' => 'active'
]
]);
}
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-08 07:20:26 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Step 1: Mark some documents for deletion by updating their status
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_2 " ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
'status' => 'marked_for_deletion'
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_4 " ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
'status' => 'marked_for_deletion'
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
// Step 2: Bulk delete all documents with status 'marked_for_deletion'
// This should delete the documents we just updated in the transaction
$response = $this -> client -> call ( Client :: METHOD_DELETE , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'queries' => [ Query :: equal ( 'status' , [ 'marked_for_deletion' ]) -> toString ()],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-08 07:20:26 +00:00
'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' ]);
2026-02-25 10:38:40 +00:00
// Verify marked documents were deleted
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_2 " ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ], 'Document marked for deletion should have been deleted' );
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_4 " ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ], 'Document marked for deletion should have been deleted' );
2026-02-25 10:38:40 +00:00
// Verify other documents still exist
2025-09-08 07:20:26 +00:00
foreach ([ 1 , 3 , 5 ] as $i ) {
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " doc_ { $i } " ), array_merge ([
2025-09-08 07:20:26 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ], " Document { $i } should still exist " );
$this -> assertEquals ( 'active' , $response [ 'body' ][ 'status' ]);
}
}
2025-09-12 12:04:49 +00:00
/**
* Test increment and decrement operations in transaction
*/
public function testIncrementDecrementOperations () : void
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'IncrementDecrementTestDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
'name' => 'CounterCollection' ,
2025-09-12 12:04:49 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-09-12 12:04:49 +00:00
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
// Add integer attributes
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " integer " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'counter' ,
'required' => false ,
'default' => 0 ,
]);
2026-03-02 06:55:40 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " integer " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'score' ,
'required' => false ,
'default' => 100 ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-09-12 12:04:49 +00:00
2026-02-25 10:38:40 +00:00
// Create initial document
$doc = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'counter_doc' ,
2025-09-12 12:04:49 +00:00
'data' => [
'counter' => 10 ,
'score' => 50
]
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 201 , $doc [ 'headers' ][ 'status-code' ]);
2025-09-12 12:04:49 +00:00
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-12 12:04:49 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
// Add increment and decrement operations
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-12 12:04:49 +00:00
'action' => 'increment' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'counter_doc' ,
2025-09-12 12:04:49 +00:00
'data' => [
2026-02-25 10:38:40 +00:00
$this -> getSchemaParam () => 'counter' ,
2025-09-12 12:04:49 +00:00
'value' => 5 ,
]
],
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-12 12:04:49 +00:00
'action' => 'decrement' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'counter_doc' ,
2025-09-12 12:04:49 +00:00
'data' => [
2026-02-25 10:38:40 +00:00
$this -> getSchemaParam () => 'score' ,
2025-09-12 12:04:49 +00:00
'value' => 20 ,
]
],
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-12 12:04:49 +00:00
'action' => 'increment' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'counter_doc' ,
2025-09-12 12:04:49 +00:00
'data' => [
2026-02-25 10:38:40 +00:00
$this -> getSchemaParam () => 'counter' ,
2025-09-12 12:04:49 +00:00
'value' => 3 ,
'max' => 20
]
],
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
2025-09-12 12:04:49 +00:00
'action' => 'decrement' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'counter_doc' ,
2025-09-12 12:04:49 +00:00
'data' => [
2026-02-25 10:38:40 +00:00
$this -> getSchemaParam () => 'score' ,
2025-09-12 12:04:49 +00:00
'value' => 30 ,
'min' => 0
]
]
]
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-12 12:04:49 +00:00
'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 final values
2026-02-25 10:38:40 +00:00
$doc = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " counter_doc " ), array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $doc [ 'headers' ][ 'status-code' ]);
2025-09-12 12:04:49 +00:00
// counter: 10 + 5 + 3 = 18 (capped at 20 max)
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 18 , $doc [ 'body' ][ 'counter' ]);
2025-09-12 12:04:49 +00:00
// score: 50 - 20 - 100 = -70, but min is 0
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 0 , $doc [ 'body' ][ 'score' ]);
2025-09-12 12:04:49 +00:00
}
2025-10-09 02:50:18 +00:00
/**
2026-02-25 10:38:40 +00:00
* Test individual increment / decrement endpoints with transactions for Legacy Collections API
* This test ensures that :
* 1. Transaction logs store the correct attribute key ( 'attribute' for Collections API )
* 2. Mock responses return the correct ID keys ( '$collectionId' not '$tableId' )
2025-10-09 02:50:18 +00:00
*/
2026-02-25 10:38:40 +00:00
public function testIncrementDecrementEndpointsWithTransaction () : void
2025-10-09 02:50:18 +00:00
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
2026-02-25 10:38:40 +00:00
'name' => 'IncrDecrEndpointTestDB'
2025-10-09 02:50:18 +00:00
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
'name' => 'AccountsCollection' ,
2025-10-09 02:50:18 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-10-09 02:50:18 +00:00
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
// Add balance attribute
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " integer " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'balance' ,
'required' => false ,
'default' => 0 ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-10-09 02:50:18 +00:00
2026-02-25 10:38:40 +00:00
// Create initial documents
$this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2026-02-25 10:38:40 +00:00
], $this -> getHeaders ()), [
$this -> getRecordIdParam () => 'joe' ,
'data' => [ 'balance' => 100 ]
2025-10-09 02:50:18 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'jane' ,
'data' => [ 'balance' => 50 ]
2025-10-09 02:50:18 +00:00
]);
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 201 , $transaction [ 'headers' ][ 'status-code' ]);
2025-10-09 02:50:18 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Test: Decrement using individual endpoint - should store 'attribute' not 'column' in transaction log
$decrementResponse = $this -> client -> call (
Client :: METHOD_PATCH ,
$this -> getRecordUrl ( $databaseId , $collectionId , 'joe' ) . '/balance/decrement' ,
array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()),
[
'transactionId' => $transactionId ,
'value' => 50 ,
'min' => 0 ,
2025-10-09 02:50:18 +00:00
]
2026-02-25 10:38:40 +00:00
);
2025-10-09 02:50:18 +00:00
2026-02-25 10:38:40 +00:00
// Test: Response should return the correct container ID key for this API
$this -> assertEquals ( 200 , $decrementResponse [ 'headers' ][ 'status-code' ]);
$this -> assertArrayHasKey ( $this -> getContainerIdResponseKey (), $decrementResponse [ 'body' ], 'Response should contain ' . $this -> getContainerIdResponseKey ());
$this -> assertArrayNotHasKey ( $this -> getOppositeContainerIdResponseKey (), $decrementResponse [ 'body' ], 'Response should not contain ' . $this -> getOppositeContainerIdResponseKey ());
$this -> assertEquals ( $collectionId , $decrementResponse [ 'body' ][ $this -> getContainerIdResponseKey ()]);
$this -> assertEquals ( $databaseId , $decrementResponse [ 'body' ][ '$databaseId' ]);
2025-10-09 02:50:18 +00:00
2026-02-25 10:38:40 +00:00
// Test increment endpoint
$incrementResponse = $this -> client -> call (
Client :: METHOD_PATCH ,
$this -> getRecordUrl ( $databaseId , $collectionId , 'jane' ) . '/balance/increment' ,
array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()),
[
'transactionId' => $transactionId ,
'value' => 50 ,
]
);
$this -> assertEquals ( 200 , $incrementResponse [ 'headers' ][ 'status-code' ]);
$this -> assertArrayHasKey ( $this -> getContainerIdResponseKey (), $incrementResponse [ 'body' ], 'Response should contain ' . $this -> getContainerIdResponseKey ());
$this -> assertArrayNotHasKey ( $this -> getOppositeContainerIdResponseKey (), $incrementResponse [ 'body' ], 'Response should not contain ' . $this -> getOppositeContainerIdResponseKey ());
$this -> assertEquals ( $collectionId , $incrementResponse [ 'body' ][ $this -> getContainerIdResponseKey ()]);
$this -> assertEquals ( $databaseId , $incrementResponse [ 'body' ][ '$databaseId' ]);
// Commit transaction - this will fail if transaction log has wrong schema param
$commitResponse = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2026-02-25 10:38:40 +00:00
], $this -> getHeaders ()), [
2025-10-09 02:50:18 +00:00
'commit' => true
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $commitResponse [ 'headers' ][ 'status-code' ], 'Transaction commit should succeed' );
2025-10-09 02:50:18 +00:00
2026-02-25 10:38:40 +00:00
// Verify final values
$joe = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " joe " ), array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2026-02-25 10:38:40 +00:00
$jane = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , " jane " ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $joe [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 50 , $joe [ 'body' ][ 'balance' ], 'Joe should have 100 - 50 = 50' );
$this -> assertEquals ( 200 , $jane [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 100 , $jane [ 'body' ][ 'balance' ], 'Jane should have 50 + 50 = 100' );
}
2025-10-09 02:50:18 +00:00
2025-10-10 04:27:09 +00:00
/**
2026-02-25 10:38:40 +00:00
* Test bulk update operations in transaction
2025-10-10 04:27:09 +00:00
*/
2026-02-25 10:38:40 +00:00
public function testBulkUpdateOperations () : void
2025-10-10 04:27:09 +00:00
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-10-10 04:27:09 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
2026-02-25 10:38:40 +00:00
'name' => 'BulkUpdateTestDB'
2025-10-10 04:27:09 +00:00
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-10-10 04:27:09 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
'name' => 'BulkUpdateCollection' ,
2025-10-10 04:27:09 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-10-10 04:27:09 +00:00
2026-02-25 10:38:40 +00:00
// Add attributes
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'status' ,
'size' => 50 ,
'required' => false ,
]);
2025-10-10 04:27:09 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'category' ,
'size' => 50 ,
'required' => false ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-10-10 04:27:09 +00:00
2026-02-25 10:38:40 +00:00
// Create initial documents
for ( $i = 1 ; $i <= 5 ; $i ++ ) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
$this -> getRecordIdParam () => " doc_ { $i } " ,
'data' => [
'status' => 'pending' ,
'category' => $i % 2 === 0 ? 'even' : 'odd'
]
]);
}
2025-10-10 04:27:09 +00:00
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-10-10 04:27:09 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Add bulk update operations
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
$this -> getContainerIdParam () => $collectionId ,
'action' => 'bulkUpdate' ,
'data' => [
'queries' => [
Query :: equal ( 'category' , [ 'even' ]) -> toString ()
],
'data' => [
'status' => 'approved'
]
]
],
[
'databaseId' => $databaseId ,
$this -> getContainerIdParam () => $collectionId ,
'action' => 'bulkUpdate' ,
'data' => [
'queries' => [
Query :: equal ( 'category' , [ 'odd' ]) -> toString ()
],
'data' => [
'status' => 'rejected'
]
]
]
2025-10-10 04:27:09 +00:00
]
2026-02-25 10:38:40 +00:00
]);
2025-10-10 04:27:09 +00:00
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2025-10-10 04:27:09 +00:00
2026-02-25 10:38:40 +00:00
// Commit transaction
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-10-10 04:27:09 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2026-02-25 10:38:40 +00:00
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2025-10-10 04:27:09 +00:00
'commit' => true
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2025-10-10 04:27:09 +00:00
2026-02-25 10:38:40 +00:00
// Verify updates
$docs = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-10-10 04:27:09 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2026-02-25 10:38:40 +00:00
foreach ( $docs [ 'body' ][ $this -> getRecordResource ()] as $doc ) {
if ( $doc [ 'category' ] === 'even' ) {
$this -> assertEquals ( 'approved' , $doc [ 'status' ]);
} else {
$this -> assertEquals ( 'rejected' , $doc [ 'status' ]);
}
}
2025-10-10 04:27:09 +00:00
}
/**
2026-02-25 10:38:40 +00:00
* Test bulk upsert operations in transaction
2025-10-10 04:27:09 +00:00
*/
2026-02-25 10:38:40 +00:00
public function testBulkUpsertOperations () : void
2025-10-10 04:27:09 +00:00
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-10-10 04:27:09 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
2026-02-25 10:38:40 +00:00
'name' => 'BulkUpsertTestDB'
2025-10-10 04:27:09 +00:00
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-10-10 04:27:09 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
'name' => 'BulkUpsertCollection' ,
2025-10-10 04:27:09 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-10-10 04:27:09 +00:00
2026-02-25 10:38:40 +00:00
// Add attributes
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 100 ,
'required' => false ,
]);
2026-02-25 10:38:40 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " integer " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'value' ,
'required' => false ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2025-10-10 04:27:09 +00:00
2026-02-25 10:38:40 +00:00
// Create some initial documents
$this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-10-10 04:27:09 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'existing_1' ,
'data' => [
'name' => 'Existing Document 1' ,
'value' => 10
]
2025-10-10 04:27:09 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
$this -> getRecordIdParam () => 'existing_2' ,
'data' => [
'name' => 'Existing Document 2' ,
'value' => 20
]
]);
// Create transaction
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-10-10 04:27:09 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Add bulk upsert operations
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
$this -> getContainerIdParam () => $collectionId ,
'action' => 'bulkUpsert' ,
'data' => [
[
'$id' => 'existing_1' ,
'name' => 'Updated Document 1' ,
'value' => 100
],
[
'$id' => 'new_1' ,
'name' => 'New Document 1' ,
'value' => 30
],
[
'$id' => 'new_2' ,
'name' => 'New Document 2' ,
'value' => 40
]
]
]
2025-10-10 04:27:09 +00:00
]
2026-02-25 10:38:40 +00:00
]);
2025-10-10 04:27:09 +00:00
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2025-10-10 04:27:09 +00:00
2026-02-25 10:38:40 +00:00
// Commit transaction
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
2025-10-10 04:27:09 +00:00
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Verify results
$docs = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-10-10 04:27:09 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 4 , $docs [ 'body' ][ 'total' ]);
$docMap = [];
foreach ( $docs [ 'body' ][ $this -> getRecordResource ()] as $doc ) {
$docMap [ $doc [ '$id' ]] = $doc ;
}
// Verify updated document
$this -> assertEquals ( 'Updated Document 1' , $docMap [ 'existing_1' ][ 'name' ]);
$this -> assertEquals ( 100 , $docMap [ 'existing_1' ][ 'value' ]);
// Verify unchanged document
$this -> assertEquals ( 'Existing Document 2' , $docMap [ 'existing_2' ][ 'name' ]);
$this -> assertEquals ( 20 , $docMap [ 'existing_2' ][ 'value' ]);
// Verify new documents
$this -> assertEquals ( 'New Document 1' , $docMap [ 'new_1' ][ 'name' ]);
$this -> assertEquals ( 30 , $docMap [ 'new_1' ][ 'value' ]);
$this -> assertEquals ( 'New Document 2' , $docMap [ 'new_2' ][ 'name' ]);
$this -> assertEquals ( 40 , $docMap [ 'new_2' ][ 'value' ]);
2025-10-10 04:27:09 +00:00
}
2026-02-25 10:38:40 +00:00
/**
* Test bulk delete operations in transaction
*/
public function testBulkDeleteOperations () : void
2025-10-09 02:50:18 +00:00
{
2026-02-25 10:38:40 +00:00
// Create database and collection
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
2026-02-25 10:38:40 +00:00
'name' => 'BulkDeleteTestDB'
2025-10-09 02:50:18 +00:00
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
'name' => 'BulkDeleteCollection' ,
2025-10-09 02:50:18 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
2026-02-25 10:38:40 +00:00
Permission :: delete ( Role :: any ()),
2025-10-09 02:50:18 +00:00
],
]);
2026-02-25 10:38:40 +00:00
$collectionId = $collection [ 'body' ][ '$id' ];
2025-10-09 02:50:18 +00:00
2026-02-25 10:38:40 +00:00
// Add attributes
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " string " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'type' ,
'size' => 50 ,
'required' => false ,
]);
2025-10-09 02:50:18 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , " integer " , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'priority' ,
'required' => false ,
]);
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2026-02-25 10:38:40 +00:00
// Create initial documents
for ( $i = 1 ; $i <= 10 ; $i ++ ) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
$this -> getRecordIdParam () => " doc_ { $i } " ,
'data' => [
'type' => $i <= 5 ? 'temp' : 'permanent' ,
'priority' => $i
]
]);
}
// Create transaction
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$transactionId = $transaction [ 'body' ][ '$id' ];
// Add bulk delete operations
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
2025-10-09 02:50:18 +00:00
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
'action' => 'bulkDelete' ,
2025-10-09 02:50:18 +00:00
'data' => [
2026-02-25 10:38:40 +00:00
'queries' => [
Query :: equal ( 'type' , [ 'temp' ]) -> toString ()
]
2025-10-09 02:50:18 +00:00
]
],
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
'action' => 'bulkDelete' ,
2025-10-09 02:50:18 +00:00
'data' => [
2026-02-25 10:38:40 +00:00
'queries' => [
Query :: greaterThan ( 'priority' , 8 ) -> toString ()
2025-10-09 02:50:18 +00:00
]
]
2026-02-25 10:38:40 +00:00
]
2025-10-09 02:50:18 +00:00
]
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2025-10-09 02:50:18 +00:00
2026-02-25 10:38:40 +00:00
// Verify deletions
$docs = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $collectionId , null ), array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2026-02-25 10:38:40 +00:00
// Should have deleted docs 1-5 (temp) and docs 9-10 (priority > 8)
// Remaining should be docs 6-8
$this -> assertEquals ( 3 , $docs [ 'body' ][ 'total' ]);
$remainingIds = array_map ( fn ( $doc ) => $doc [ '$id' ], $docs [ 'body' ][ $this -> getRecordResource ()]);
sort ( $remainingIds );
$this -> assertEquals ([ 'doc_6' , 'doc_7' , 'doc_8' ], $remainingIds );
2025-10-09 02:50:18 +00:00
}
/**
2026-02-25 10:38:40 +00:00
* Test validation for invalid operation inputs
2025-10-09 02:50:18 +00:00
*/
2026-02-25 10:38:40 +00:00
public function testCreateOperationsValidation () : void
2025-10-09 02:50:18 +00:00
{
2026-02-25 10:38:40 +00:00
// Create database and collection for testing
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
2026-02-25 10:38:40 +00:00
'name' => 'ValidationTestDatabase'
2025-10-09 02:50:18 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 201 , $database [ 'headers' ][ 'status-code' ]);
2025-10-09 02:50:18 +00:00
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$collection = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
'name' => 'ValidationTest' ,
$this -> getSecurityParam () => false ,
2025-10-09 02:50:18 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
2026-02-25 10:38:40 +00:00
Permission :: update ( Role :: any ()),
2025-10-09 02:50:18 +00:00
Permission :: delete ( Role :: any ()),
],
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 201 , $collection [ 'headers' ][ 'status-code' ]);
$collectionId = $collection [ 'body' ][ '$id' ];
2025-10-09 02:50:18 +00:00
2026-02-25 10:38:40 +00:00
// Add required attribute
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$attribute = $this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $collectionId , 'string' , null ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2025-10-09 02:50:18 +00:00
2026-03-19 15:00:42 +00:00
$this -> assertEquals ( 202 , $attribute [ 'headers' ][ 'status-code' ]);
2025-10-09 02:50:18 +00:00
2026-03-19 15:00:42 +00:00
// Wait for attribute to be ready
$this -> waitForAllAttributes ( $databaseId , $collectionId );
}
2026-02-25 10:38:40 +00:00
// Create transaction
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 201 , $transaction [ 'headers' ][ 'status-code' ]);
2025-10-09 02:50:18 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Test 1: Invalid action type
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
2026-02-25 10:38:40 +00:00
'action' => 'invalidAction' ,
2025-10-09 02:50:18 +00:00
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
$this -> getRecordIdParam () => ID :: unique (),
'data' => [ 'name' => 'Test' ]
]
2025-10-09 02:50:18 +00:00
]
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2025-10-09 02:50:18 +00:00
2026-02-25 10:38:40 +00:00
// Test 2: Missing required action field
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'operations' => [
[
'databaseId' => $databaseId ,
$this -> getContainerIdParam () => $collectionId ,
$this -> getRecordIdParam () => ID :: unique (),
'data' => [ 'name' => 'Test' ]
]
]
2025-10-09 02:50:18 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2025-10-09 02:50:18 +00:00
2026-02-25 10:38:40 +00:00
// Test 3: Missing required databaseId field
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'operations' => [
[
'action' => 'create' ,
$this -> getContainerIdParam () => $collectionId ,
$this -> getRecordIdParam () => ID :: unique (),
'data' => [ 'name' => 'Test' ]
]
]
2025-10-09 02:50:18 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2025-10-09 02:50:18 +00:00
2026-02-25 10:38:40 +00:00
// Test 4: Missing required containerId field
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'operations' => [
[
'action' => 'create' ,
'databaseId' => $databaseId ,
$this -> getRecordIdParam () => ID :: unique (),
'data' => [ 'name' => 'Test' ]
]
]
2025-10-09 02:50:18 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2025-10-09 02:50:18 +00:00
2026-02-25 10:38:40 +00:00
// Test 5: Missing recordId for create operation
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'operations' => [
[
'action' => 'create' ,
'databaseId' => $databaseId ,
$this -> getContainerIdParam () => $collectionId ,
'data' => [ 'name' => 'Test' ]
]
]
2025-10-09 02:50:18 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2025-10-09 02:50:18 +00:00
2026-02-25 10:38:40 +00:00
// Test 5: Missing data for create operation
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'action' => 'create' ,
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
$this -> getRecordIdParam () => ID :: unique ()
]
2025-10-09 02:50:18 +00:00
]
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2025-10-09 02:50:18 +00:00
2026-02-25 10:38:40 +00:00
// Test 6: BulkCreate with non-array data
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-10-09 02:50:18 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'operations' => [
[
'action' => 'bulkCreate' ,
'databaseId' => $databaseId ,
$this -> getContainerIdParam () => $collectionId ,
'data' => 'not an array'
]
]
2025-10-09 02:50:18 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2025-10-09 02:50:18 +00:00
2026-02-25 10:38:40 +00:00
// Test 7: BulkUpdate with missing queries
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'operations' => [
[
'action' => 'bulkUpdate' ,
'databaseId' => $databaseId ,
$this -> getContainerIdParam () => $collectionId ,
'data' => [
'data' => [ 'name' => 'Updated' ]
]
]
]
2025-09-12 12:04:49 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2025-09-12 12:04:49 +00:00
2026-02-25 10:38:40 +00:00
// Test 8: BulkUpdate with invalid query format
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'operations' => [
[
'action' => 'bulkUpdate' ,
'databaseId' => $databaseId ,
$this -> getContainerIdParam () => $collectionId ,
'data' => [
'queries' => 'not an array' ,
'data' => [ 'name' => 'Updated' ]
]
]
]
2025-09-12 12:04:49 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2025-09-12 12:04:49 +00:00
2026-02-25 10:38:40 +00:00
// Test 9: BulkDelete with missing queries
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'operations' => [
[
'action' => 'bulkDelete' ,
'databaseId' => $databaseId ,
$this -> getContainerIdParam () => $collectionId ,
'data' => []
]
]
2025-09-12 12:04:49 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
// Test 10: Increment with missing attribute
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'operations' => [
[
'action' => 'increment' ,
'databaseId' => $databaseId ,
$this -> getContainerIdParam () => $collectionId ,
$this -> getRecordIdParam () => ID :: unique (),
'data' => [ 'value' => 1 ]
2025-09-12 12:04:49 +00:00
]
2026-02-25 10:38:40 +00:00
]
]);
2025-09-12 12:04:49 +00:00
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2025-09-12 12:04:49 +00:00
2026-02-25 10:38:40 +00:00
// Test 11: Decrement with invalid value type
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
2026-02-25 10:38:40 +00:00
'action' => 'decrement' ,
2025-09-12 12:04:49 +00:00
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $collectionId ,
$this -> getRecordIdParam () => ID :: unique (),
2025-09-12 12:04:49 +00:00
'data' => [
2026-02-25 10:38:40 +00:00
$this -> getSchemaParam () => 'counter' ,
'value' => 'not a number'
2025-09-12 12:04:49 +00:00
]
]
]
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2025-09-12 12:04:49 +00:00
2026-02-25 10:38:40 +00:00
// Test 12: Empty operations array
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'operations' => []
2025-09-12 12:04:49 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2025-09-12 12:04:49 +00:00
2026-02-25 10:38:40 +00:00
// Test 13: Operations not an array
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2026-02-25 10:38:40 +00:00
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => 'not an array'
]);
2025-09-12 12:04:49 +00:00
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2025-09-12 12:04:49 +00:00
}
/**
2026-02-25 10:38:40 +00:00
* Test validation for committing / rolling back transactions
2025-09-12 12:04:49 +00:00
*/
2026-02-25 10:38:40 +00:00
public function testCommitRollbackValidation () : void
2025-09-12 12:04:49 +00:00
{
2026-02-25 10:38:40 +00:00
// Create transaction
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 201 , $transaction [ 'headers' ][ 'status-code' ]);
$transactionId = $transaction [ 'body' ][ '$id' ];
// Test 1: Missing both commit and rollback
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), []);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
// Test 2: Both commit and rollback set to true
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'commit' => true ,
'rollback' => true
2025-09-12 12:04:49 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2025-09-12 12:04:49 +00:00
2026-02-25 10:38:40 +00:00
// Test 3: Invalid transaction ID
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( " invalid_id " ), array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'commit' => true
2025-09-12 12:04:49 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
2025-09-12 12:04:49 +00:00
2026-02-25 10:38:40 +00:00
// Commit the transaction
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'commit' => true
2025-09-12 12:04:49 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Test 4: Attempt to commit already committed transaction
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'commit' => true
2025-09-12 12:04:49 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
}
2025-09-12 12:04:49 +00:00
2026-02-25 10:38:40 +00:00
/**
* Test validation for non - existent resources
*/
public function testNonExistentResources () : void
{
// Create database and transaction
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2026-02-25 10:38:40 +00:00
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'ResourceTestDatabase'
2025-09-12 12:04:49 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 201 , $database [ 'headers' ][ 'status-code' ]);
$databaseId = $database [ 'body' ][ '$id' ];
2025-09-12 12:04:49 +00:00
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-12 12:04:49 +00:00
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 201 , $transaction [ 'headers' ][ 'status-code' ]);
2025-09-12 12:04:49 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Test 1: Non-existent database
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
2026-02-25 10:38:40 +00:00
'action' => 'create' ,
'databaseId' => 'nonExistentDatabase' ,
$this -> getContainerIdParam () => 'someCollection' ,
$this -> getRecordIdParam () => ID :: unique (),
'data' => [ 'name' => 'Test' ]
2025-09-12 12:04:49 +00:00
]
]
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
2025-09-12 12:04:49 +00:00
2026-02-25 10:38:40 +00:00
// Test 2: Non-existent collection
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . " /operations " , array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'operations' => [
[
'action' => 'create' ,
'databaseId' => $databaseId ,
$this -> getContainerIdParam () => 'nonExistentCollection' ,
$this -> getRecordIdParam () => ID :: unique (),
'data' => [ 'name' => 'Test' ]
]
]
2025-09-12 12:04:49 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
2025-09-12 12:04:49 +00:00
}
/**
2026-02-25 10:38:40 +00:00
* Test increment followed by update in same transaction
* TablesDB - specific test
2025-09-12 12:04:49 +00:00
*/
2026-02-25 10:38:40 +00:00
public function testIncrementThenUpdate () : void
2025-09-12 12:04:49 +00:00
{
2026-02-25 10:38:40 +00:00
if ( $this -> getDatabaseType () !== 'tablesdb' ) {
$this -> markTestSkipped ( 'Test only applicable to TablesDB' );
}
2025-09-12 12:04:49 +00:00
// Create database and table
2026-02-25 10:38:40 +00:00
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
2026-02-25 10:38:40 +00:00
'name' => 'IncrementUpdateTestDB'
2025-09-12 12:04:49 +00:00
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$table = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
'name' => 'CounterTable' ,
2025-09-12 12:04:49 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
2026-02-25 10:38:40 +00:00
Permission :: update ( Role :: any ()),
2025-09-12 12:04:49 +00:00
],
]);
$tableId = $table [ 'body' ][ '$id' ];
// Add columns
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $tableId , 'integer' ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'counter' ,
'required' => false ,
'default' => 0 ,
]);
2025-09-12 12:04:49 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $tableId , 'string' ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'status' ,
'size' => 50 ,
'required' => false ,
]);
2025-09-12 12:04:49 +00:00
2026-03-19 15:00:42 +00:00
$this -> waitForAllAttributes ( $databaseId , $tableId );
}
2025-09-12 12:04:49 +00:00
2026-02-25 10:38:40 +00:00
// Create initial row
$row = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $tableId ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
$this -> getRecordIdParam () => 'test_row' ,
'data' => [
'counter' => 10 ,
'status' => 'initial'
]
]);
$this -> assertEquals ( 201 , $row [ 'headers' ][ 'status-code' ]);
2025-09-12 12:04:49 +00:00
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-09-12 12:04:49 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Add operations: increment then update
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . '/operations' , array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $tableId ,
'action' => 'increment' ,
$this -> getRecordIdParam () => 'test_row' ,
2025-09-12 12:04:49 +00:00
'data' => [
2026-02-25 10:38:40 +00:00
'column' => 'counter' ,
'value' => 5 ,
2025-09-12 12:04:49 +00:00
]
],
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $tableId ,
'action' => 'update' ,
$this -> getRecordIdParam () => 'test_row' ,
2025-09-12 12:04:49 +00:00
'data' => [
2026-02-25 10:38:40 +00:00
'status' => 'updated'
2025-09-12 12:04:49 +00:00
]
2026-02-25 10:38:40 +00:00
],
2025-09-12 12:04:49 +00:00
]
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-09-12 12:04:49 +00:00
'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' ]);
2026-02-25 10:38:40 +00:00
// Verify final values
$row = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $tableId , 'test_row' ), array_merge ([
2025-09-12 12:04:49 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $row [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 15 , $row [ 'body' ][ 'counter' ], 'Counter should be incremented: 10 + 5 = 15' );
$this -> assertEquals ( 'updated' , $row [ 'body' ][ 'status' ], 'Status should be updated' );
2025-09-12 12:04:49 +00:00
}
2025-10-03 04:34:34 +00:00
/**
2026-02-25 10:38:40 +00:00
* Test cross - API compatibility for increment / decrement
* TablesDB - specific test
2025-10-03 04:34:34 +00:00
*/
2026-02-25 10:38:40 +00:00
public function testCrossAPIIncrementDecrement () : void
2025-10-03 04:34:34 +00:00
{
2026-02-25 10:38:40 +00:00
if ( $this -> getDatabaseType () !== 'tablesdb' ) {
$this -> markTestSkipped ( 'Test only applicable to TablesDB' );
}
// Create database and table
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
2026-02-25 10:38:40 +00:00
'name' => 'CrossAPITestDB'
2025-10-03 04:34:34 +00:00
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$table = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
'name' => 'CrossAPITable' ,
2025-10-03 04:34:34 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
],
]);
$tableId = $table [ 'body' ][ '$id' ];
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
// Add balance column
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $tableId , 'integer' ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'balance' ,
'required' => false ,
'default' => 0 ,
]);
$this -> waitForAllAttributes ( $databaseId , $tableId );
}
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
// Create initial row
$this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $tableId ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
$this -> getRecordIdParam () => 'test' ,
'data' => [ 'balance' => 100 ]
]);
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
// Create transaction using TablesDB API
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-10-03 04:34:34 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Stage operations using TablesDB API
$this -> client -> call (
Client :: METHOD_PATCH ,
$this -> getRecordUrl ( $databaseId , $tableId , 'test' ) . '/balance/decrement' ,
array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()),
[
'transactionId' => $transactionId ,
'value' => 30 ,
2025-10-03 04:34:34 +00:00
]
2026-02-25 10:38:40 +00:00
);
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
// Commit using Collections API
$commitResponse = $this -> client -> call (
Client :: METHOD_PATCH ,
" /databases/transactions/ { $transactionId } " ,
array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()),
[
'commit' => true
2025-10-03 04:34:34 +00:00
]
2026-02-25 10:38:40 +00:00
);
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $commitResponse [ 'headers' ][ 'status-code' ], 'Cross-API commit should succeed' );
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
// Verify final value
$row = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $tableId , 'test' ), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2026-02-25 10:38:40 +00:00
], $this -> getHeaders ()));
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $row [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 70 , $row [ 'body' ][ 'balance' ], 'Balance should be 100 - 30 = 70' );
}
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
/**
* Test bulk update with dependent documents
* TablesDB - specific test
*/
public function testBulkUpdateWithDependentDocuments () : void
{
if ( $this -> getDatabaseType () !== 'tablesdb' ) {
$this -> markTestSkipped ( 'Test only applicable to TablesDB' );
}
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
// Create database and table
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'databaseId' => ID :: unique (),
'name' => 'BulkUpdateDependentDB'
2025-10-03 04:34:34 +00:00
]);
2026-02-25 10:38:40 +00:00
$databaseId = $database [ 'body' ][ '$id' ];
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
$table = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
'name' => 'TestTable' ,
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
],
2025-10-03 04:34:34 +00:00
]);
2026-02-25 10:38:40 +00:00
$tableId = $table [ 'body' ][ '$id' ];
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
// Add columns
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $tableId , 'string' ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'status' ,
'size' => 50 ,
'required' => false ,
]);
2025-10-03 04:34:34 +00:00
2026-03-19 15:00:42 +00:00
$this -> waitForAllAttributes ( $databaseId , $tableId );
}
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
// Create transaction
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2026-02-25 10:38:40 +00:00
], $this -> getHeaders ()));
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
// Create a document, then bulk update it
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . '/operations' , array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $tableId ,
'action' => 'create' ,
$this -> getRecordIdParam () => 'doc1' ,
2025-10-03 04:34:34 +00:00
'data' => [
2026-02-25 10:38:40 +00:00
'status' => 'pending'
2025-10-03 04:34:34 +00:00
]
2026-02-25 10:38:40 +00:00
],
[
'databaseId' => $databaseId ,
$this -> getContainerIdParam () => $tableId ,
'action' => 'bulkUpdate' ,
'data' => [
'queries' => [],
'data' => [
'status' => 'approved'
]
]
],
2025-10-03 04:34:34 +00:00
]
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
// Commit transaction
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'commit' => true
2025-10-03 04:34:34 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ], 'Bulk update should succeed on dependent documents' );
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
// Verify the document was updated
$row = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $tableId , 'doc1' ), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2026-02-25 10:38:40 +00:00
], $this -> getHeaders ()));
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $row [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'approved' , $row [ 'body' ][ 'status' ]);
}
/**
* Test bulk delete with dependent documents
* TablesDB - specific test
*/
public function testBulkDeleteWithDependentDocuments () : void
{
if ( $this -> getDatabaseType () !== 'tablesdb' ) {
$this -> markTestSkipped ( 'Test only applicable to TablesDB' );
}
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'databaseId' => ID :: unique (),
'name' => 'BulkDeleteDependentDB'
2025-10-03 04:34:34 +00:00
]);
2026-02-25 10:38:40 +00:00
$databaseId = $database [ 'body' ][ '$id' ];
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
$table = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
'name' => 'TestTable' ,
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
2025-10-03 04:34:34 +00:00
]);
2026-02-25 10:38:40 +00:00
$tableId = $table [ 'body' ][ '$id' ];
2025-10-03 04:34:34 +00:00
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $tableId , 'string' ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 50 ,
'required' => false ,
]);
$this -> waitForAllAttributes ( $databaseId , $tableId );
}
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-10-03 04:34:34 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Create then bulk delete
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . '/operations' , array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
2026-02-25 10:38:40 +00:00
]), [
'operations' => [
[
'databaseId' => $databaseId ,
$this -> getContainerIdParam () => $tableId ,
'action' => 'create' ,
$this -> getRecordIdParam () => 'doc1' ,
'data' => [
'name' => 'Test'
]
],
[
'databaseId' => $databaseId ,
$this -> getContainerIdParam () => $tableId ,
'action' => 'bulkDelete' ,
'data' => [
'queries' => [],
]
],
]
]);
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'commit' => true
2025-10-03 04:34:34 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ], 'Bulk delete should succeed on dependent documents' );
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
// Verify document was deleted
$rows = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $tableId ), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2026-02-25 10:38:40 +00:00
], $this -> getHeaders ()));
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 0 , $rows [ 'body' ][ 'total' ]);
}
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
/**
* Test bulk upsert with dependent documents
* TablesDB - specific test
*/
public function testBulkUpsertWithDependentDocuments () : void
{
if ( $this -> getDatabaseType () !== 'tablesdb' ) {
$this -> markTestSkipped ( 'Test only applicable to TablesDB' );
}
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'databaseId' => ID :: unique (),
'name' => 'BulkUpsertDependentDB'
2025-10-03 04:34:34 +00:00
]);
2026-02-25 10:38:40 +00:00
$databaseId = $database [ 'body' ][ '$id' ];
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
$table = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
'name' => 'TestTable' ,
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
],
2025-10-03 04:34:34 +00:00
]);
2026-02-25 10:38:40 +00:00
$tableId = $table [ 'body' ][ '$id' ];
2025-10-03 04:34:34 +00:00
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $tableId , 'string' ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'status' ,
'size' => 50 ,
'required' => false ,
]);
$this -> waitForAllAttributes ( $databaseId , $tableId );
}
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-10-04 03:43:39 +00:00
], $this -> getHeaders ()));
2025-10-03 04:34:34 +00:00
$transactionId = $transaction [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Create then bulk upsert same document
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . '/operations' , array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
2026-02-25 10:38:40 +00:00
'databaseId' => $databaseId ,
$this -> getContainerIdParam () => $tableId ,
2025-10-03 04:34:34 +00:00
'action' => 'create' ,
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'doc1' ,
'data' => [
'status' => 'pending'
]
],
[
'databaseId' => $databaseId ,
$this -> getContainerIdParam () => $tableId ,
'action' => 'bulkUpsert' ,
'data' => [
[
'$id' => 'doc1' ,
'status' => 'approved'
]
]
],
2025-10-03 04:34:34 +00:00
]
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2025-10-03 04:34:34 +00:00
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-10-03 04:34:34 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
'commit' => true
2025-10-03 04:34:34 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ], 'Bulk upsert should succeed on dependent documents' );
$row = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $tableId , 'doc1' ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $row [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'approved' , $row [ 'body' ][ 'status' ]);
2025-10-03 04:34:34 +00:00
}
2025-10-07 09:17:59 +00:00
/**
2026-02-25 10:38:40 +00:00
* Test bulk update matches documents created in same transaction
* TablesDB - specific test
2025-10-07 09:17:59 +00:00
*/
public function testBulkUpdateMatchesCreatedDocsInSameTransaction () : void
{
2026-02-25 10:38:40 +00:00
if ( $this -> getDatabaseType () !== 'tablesdb' ) {
$this -> markTestSkipped ( 'Test only applicable to TablesDB' );
}
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-10-07 09:17:59 +00:00
'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' ];
2026-02-25 10:38:40 +00:00
$table = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-10-07 09:17:59 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-10-07 09:17:59 +00:00
'name' => 'TestTable' ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$tableId = $table [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $tableId , 'string' ), array_merge ([
2025-10-07 09:17:59 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'status' ,
'size' => 256 ,
'required' => true ,
]);
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $tableId , '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 ,
]);
$this -> waitForAllAttributes ( $databaseId , $tableId );
}
2025-10-07 09:17:59 +00:00
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-10-07 09:17:59 +00:00
'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 ++ ) {
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $tableId ), array_merge ([
2025-10-07 09:17:59 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'test_' . $i ,
2025-10-07 09:17:59 +00:00
'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'
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getRecordUrl ( $databaseId , $tableId ), array_merge ([
2025-10-07 09:17:59 +00:00
'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
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-10-07 09:17:59 +00:00
'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 ) {
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $tableId , $docId ), array_merge ([
2025-10-07 09:17:59 +00:00
'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' );
}
}
2025-10-09 03:24:25 +00:00
/**
* Test upsert with auto - generated ID followed by update
2026-02-25 10:38:40 +00:00
* TablesDB - specific test
2025-10-09 03:24:25 +00:00
*/
public function testUpsertAutoIdThenUpdate () : void
{
2026-02-25 10:38:40 +00:00
if ( $this -> getDatabaseType () !== 'tablesdb' ) {
$this -> markTestSkipped ( 'Test only applicable to TablesDB' );
}
2025-10-09 03:24:25 +00:00
// Create database and table
2026-02-25 10:38:40 +00:00
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-10-09 03:24:25 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'UpsertAutoIDTestDB'
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
$table = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-10-09 03:24:25 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-10-09 03:24:25 +00:00
'name' => 'TestCollection' ,
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
],
]);
$tableId = $table [ 'body' ][ '$id' ];
// Create columns
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $tableId , 'string' ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2025-10-09 03:24:25 +00:00
2026-03-19 15:00:42 +00:00
$this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $tableId , 'integer' ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'counter' ,
'required' => false ,
'min' => 0 ,
'max' => 10000 ,
]);
2025-10-09 03:24:25 +00:00
2026-03-19 15:00:42 +00:00
$this -> waitForAllAttributes ( $databaseId , $tableId );
}
2025-10-09 03:24:25 +00:00
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-10-09 03:24:25 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$transactionId = $transaction [ 'body' ][ '$id' ];
// First create a document in the transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $tableId ), array_merge ([
2025-10-09 03:24:25 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => ID :: unique (),
2025-10-09 03:24:25 +00:00
'data' => [
'name' => 'Initial document' ,
'counter' => 5
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$docId = $response [ 'body' ][ '$id' ];
2026-02-25 10:38:40 +00:00
// Now upsert the same document
$response = $this -> client -> call ( Client :: METHOD_PUT , $this -> getRecordUrl ( $databaseId , $tableId , $docId ), array_merge ([
2025-10-09 03:24:25 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
'name' => 'Upserted in transaction' ,
'counter' => 10
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
// Now try to update the same document again in the same transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getRecordUrl ( $databaseId , $tableId , $docId ), array_merge ([
2025-10-09 03:24:25 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
'name' => 'Updated after upsert' ,
'counter' => 20
],
'transactionId' => $transactionId
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-10-09 03:24:25 +00:00
'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 the document has the final updated values
2026-02-25 10:38:40 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $tableId , $docId ), array_merge ([
2025-10-09 03:24:25 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'Updated after upsert' , $response [ 'body' ][ 'name' ]);
$this -> assertEquals ( 20 , $response [ 'body' ][ 'counter' ]);
}
2025-11-11 07:20:08 +00:00
/**
* Test array operators in transactions using updateRow with transactionId
2026-02-25 10:38:40 +00:00
* TablesDB - specific test
2025-11-11 07:20:08 +00:00
*/
public function testArrayOperatorsWithUpdateRow () : void
{
2026-02-25 10:38:40 +00:00
if ( ! $this -> getSupportForOperators ()) {
$this -> expectNotToPerformAssertions ();
return ;
}
2025-11-11 07:20:08 +00:00
// Create database
2026-02-25 10:38:40 +00:00
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'ArrayOperatorsTestDB'
]);
$this -> assertEquals ( 201 , $database [ 'headers' ][ 'status-code' ]);
$databaseId = $database [ 'body' ][ '$id' ];
// Create table with array column
2026-02-25 10:38:40 +00:00
$table = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-11-11 07:20:08 +00:00
'name' => 'Items' ,
2025-11-12 08:04:17 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
2025-11-11 07:20:08 +00:00
]);
$this -> assertEquals ( 201 , $table [ 'headers' ][ 'status-code' ]);
$tableId = $table [ 'body' ][ '$id' ];
// Create array column
2026-03-19 15:00:42 +00:00
if ( $this -> getSupportForAttributes ()) {
$column = $this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $tableId , 'string' ), array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'items' ,
'size' => 255 ,
'required' => false ,
'array' => true ,
]);
2025-11-11 07:20:08 +00:00
2026-03-19 15:00:42 +00:00
$this -> assertEquals ( 202 , $column [ 'headers' ][ 'status-code' ]);
$this -> waitForAllAttributes ( $databaseId , $tableId );
}
2025-11-11 07:20:08 +00:00
// Create initial row with some items
2026-02-25 10:38:40 +00:00
$row = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $tableId ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'test-row' ,
2025-11-11 07:20:08 +00:00
'data' => [
'items' => [ 'item1' , 'item2' , 'item3' , 'item4' ]
]
]);
$this -> assertEquals ( 201 , $row [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ([ 'item1' , 'item2' , 'item3' , 'item4' ], $row [ 'body' ][ 'items' ]);
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 201 , $transaction [ 'headers' ][ 'status-code' ]);
$transactionId = $transaction [ 'body' ][ '$id' ];
// Test arrayRemove operator
2026-02-25 10:38:40 +00:00
$updateResponse = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getRecordUrl ( $databaseId , $tableId , 'test-row' ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'transactionId' => $transactionId ,
'data' => [
2025-11-14 04:31:17 +00:00
'items' => Operator :: arrayRemove ( 'item2' ) -> toString ()
2025-11-11 07:20:08 +00:00
]
]);
$this -> assertEquals ( 200 , $updateResponse [ 'headers' ][ 'status-code' ]);
// Test arrayInsert operator
2026-02-25 10:38:40 +00:00
$updateResponse = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getRecordUrl ( $databaseId , $tableId , 'test-row' ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'transactionId' => $transactionId ,
'data' => [
2025-11-14 04:31:17 +00:00
'items' => Operator :: arrayInsert ( 2 , 'newItem' ) -> toString ()
2025-11-11 07:20:08 +00:00
]
]);
$this -> assertEquals ( 200 , $updateResponse [ 'headers' ][ 'status-code' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$commitResponse = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'commit' => true
]);
$this -> assertEquals ( 200 , $commitResponse [ 'headers' ][ 'status-code' ]);
// Verify the operations were applied correctly
2026-02-25 10:38:40 +00:00
$row = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $tableId , 'test-row' ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $row [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ([ 'item1' , 'item3' , 'newItem' , 'item4' ], $row [ 'body' ][ 'items' ]);
}
/**
* Test array operators in transactions using createOperations
2026-02-25 10:38:40 +00:00
* TablesDB - specific test
2025-11-11 07:20:08 +00:00
*/
public function testArrayOperatorsWithCreateOperations () : void
{
2026-02-25 10:38:40 +00:00
if ( ! $this -> getSupportForOperators ()) {
$this -> expectNotToPerformAssertions ();
return ;
}
2025-11-11 07:20:08 +00:00
// Create database
2026-02-25 10:38:40 +00:00
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'ArrayOperatorsBulkTestDB'
]);
$this -> assertEquals ( 201 , $database [ 'headers' ][ 'status-code' ]);
$databaseId = $database [ 'body' ][ '$id' ];
// Create table with array column
2026-02-25 10:38:40 +00:00
$table = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-11-11 07:20:08 +00:00
'name' => 'Tags' ,
2025-11-12 08:04:17 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
2025-11-11 07:20:08 +00:00
]);
$this -> assertEquals ( 201 , $table [ 'headers' ][ 'status-code' ]);
$tableId = $table [ 'body' ][ '$id' ];
// Create array column
2026-02-25 10:38:40 +00:00
$column = $this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $tableId , 'string' ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2025-11-11 08:10:11 +00:00
'key' => 'tags' ,
2025-11-11 07:20:08 +00:00
'size' => 255 ,
'required' => false ,
'array' => true ,
]);
$this -> assertEquals ( 202 , $column [ 'headers' ][ 'status-code' ]);
2026-02-25 10:38:40 +00:00
$this -> waitForAllAttributes ( $databaseId , $tableId );
2025-11-11 07:20:08 +00:00
// Create initial row
2026-02-25 10:38:40 +00:00
$row = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $tableId ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'doc1' ,
2025-11-11 07:20:08 +00:00
'data' => [
'tags' => [ 'php' , 'javascript' , 'python' , 'ruby' ]
]
]);
$this -> assertEquals ( 201 , $row [ 'headers' ][ 'status-code' ]);
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 201 , $transaction [ 'headers' ][ 'status-code' ]);
$transactionId = $transaction [ 'body' ][ '$id' ];
// Create operations using bulk createOperations endpoint with array operators
2026-02-25 10:38:40 +00:00
$operations = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl ( $transactionId ) . '/operations' , array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'operations' => [
[
'action' => 'update' ,
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $tableId ,
$this -> getRecordIdParam () => 'doc1' ,
2025-11-11 07:20:08 +00:00
'data' => [
2025-11-14 04:31:17 +00:00
'tags' => Operator :: arrayRemove ( 'javascript' ) -> toString ()
2025-11-11 07:20:08 +00:00
]
],
[
'action' => 'update' ,
'databaseId' => $databaseId ,
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => $tableId ,
$this -> getRecordIdParam () => 'doc1' ,
2025-11-11 07:20:08 +00:00
'data' => [
2025-11-14 04:31:17 +00:00
'tags' => Operator :: arrayAppend ([ 'go' , 'rust' ]) -> toString ()
2025-11-11 07:20:08 +00:00
]
]
]
]);
$this -> assertEquals ( 201 , $operations [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 2 , $operations [ 'body' ][ 'operations' ]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$commitResponse = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'commit' => true
]);
$this -> assertEquals ( 200 , $commitResponse [ 'headers' ][ 'status-code' ]);
// Verify the operations were applied correctly
2026-02-25 10:38:40 +00:00
$row = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $tableId , 'doc1' ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $row [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ([ 'php' , 'python' , 'ruby' , 'go' , 'rust' ], $row [ 'body' ][ 'tags' ]);
}
/**
* Test multiple array operators in a single transaction
2026-02-25 10:38:40 +00:00
* TablesDB - specific test
2025-11-11 07:20:08 +00:00
*/
public function testMultipleArrayOperators () : void
{
2026-02-25 10:38:40 +00:00
if ( ! $this -> getSupportForOperators ()) {
$this -> expectNotToPerformAssertions ();
return ;
}
2025-11-11 07:20:08 +00:00
// Create database
2026-02-25 10:38:40 +00:00
$database = $this -> client -> call ( Client :: METHOD_POST , $this -> getDatabaseUrl (), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'MultipleOperatorsTestDB'
]);
$this -> assertEquals ( 201 , $database [ 'headers' ][ 'status-code' ]);
$databaseId = $database [ 'body' ][ '$id' ];
// Create table
2026-02-25 10:38:40 +00:00
$table = $this -> client -> call ( Client :: METHOD_POST , $this -> getContainerUrl ( $databaseId ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2026-02-25 10:38:40 +00:00
$this -> getContainerIdParam () => ID :: unique (),
2025-11-11 07:20:08 +00:00
'name' => 'Arrays' ,
2025-11-12 08:04:17 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
2025-11-11 07:20:08 +00:00
]);
$this -> assertEquals ( 201 , $table [ 'headers' ][ 'status-code' ]);
$tableId = $table [ 'body' ][ '$id' ];
// Create multiple array columns
$columns = [
[ 'columnId' => 'list1' , 'name' => 'List1' ],
[ 'columnId' => 'list2' , 'name' => 'List2' ],
[ 'columnId' => 'list3' , 'name' => 'List3' ],
];
foreach ( $columns as $col ) {
2026-02-25 10:38:40 +00:00
$column = $this -> client -> call ( Client :: METHOD_POST , $this -> getSchemaUrl ( $databaseId , $tableId , 'string' ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2025-11-11 08:10:11 +00:00
'key' => $col [ 'columnId' ],
2025-11-11 07:20:08 +00:00
'size' => 255 ,
'required' => false ,
'array' => true ,
]);
$this -> assertEquals ( 202 , $column [ 'headers' ][ 'status-code' ]);
}
2026-02-25 10:38:40 +00:00
$this -> waitForAllAttributes ( $databaseId , $tableId );
2025-11-11 07:20:08 +00:00
// Create initial row
2026-02-25 10:38:40 +00:00
$row = $this -> client -> call ( Client :: METHOD_POST , $this -> getRecordUrl ( $databaseId , $tableId ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
2026-02-25 10:38:40 +00:00
$this -> getRecordIdParam () => 'multi-ops' ,
2025-11-11 07:20:08 +00:00
'data' => [
'list1' => [ 'a' , 'b' , 'c' ],
'list2' => [ 'x' , 'y' , 'z' ],
'list3' => [ '1' , '2' , '3' , '4' , '5' ]
]
]);
$this -> assertEquals ( 201 , $row [ 'headers' ][ 'status-code' ]);
// Create transaction
2026-02-25 10:38:40 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , $this -> getTransactionUrl (), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 201 , $transaction [ 'headers' ][ 'status-code' ]);
$transactionId = $transaction [ 'body' ][ '$id' ];
// Test arrayPrepend
2026-02-25 10:38:40 +00:00
$this -> client -> call ( Client :: METHOD_PATCH , $this -> getRecordUrl ( $databaseId , $tableId , 'multi-ops' ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'transactionId' => $transactionId ,
'data' => [
2025-11-14 04:31:17 +00:00
'list1' => Operator :: arrayPrepend ([ 'z' ]) -> toString ()
2025-11-11 07:20:08 +00:00
]
]);
// Test arrayAppend
2026-02-25 10:38:40 +00:00
$this -> client -> call ( Client :: METHOD_PATCH , $this -> getRecordUrl ( $databaseId , $tableId , 'multi-ops' ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'transactionId' => $transactionId ,
'data' => [
2025-11-14 04:31:17 +00:00
'list2' => Operator :: arrayAppend ([ 'w' ]) -> toString ()
2025-11-11 07:20:08 +00:00
]
]);
// Test arrayRemove
2026-02-25 10:38:40 +00:00
$this -> client -> call ( Client :: METHOD_PATCH , $this -> getRecordUrl ( $databaseId , $tableId , 'multi-ops' ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'transactionId' => $transactionId ,
'data' => [
2025-11-14 04:31:17 +00:00
'list3' => Operator :: arrayRemove ( '3' ) -> toString ()
2025-11-11 07:20:08 +00:00
]
]);
// Commit transaction
2026-02-25 10:38:40 +00:00
$commitResponse = $this -> client -> call ( Client :: METHOD_PATCH , $this -> getTransactionUrl ( $transactionId ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'commit' => true
]);
$this -> assertEquals ( 200 , $commitResponse [ 'headers' ][ 'status-code' ]);
// Verify all operations were applied correctly
2026-02-25 10:38:40 +00:00
$row = $this -> client -> call ( Client :: METHOD_GET , $this -> getRecordUrl ( $databaseId , $tableId , 'multi-ops' ), array_merge ([
2025-11-11 07:20:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $row [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ([ 'z' , 'a' , 'b' , 'c' ], $row [ 'body' ][ 'list1' ], 'arrayPrepend should add element at the beginning' );
$this -> assertEquals ([ 'x' , 'y' , 'z' , 'w' ], $row [ 'body' ][ 'list2' ], 'arrayAppend should add element at the end' );
$this -> assertEquals ([ '1' , '2' , '4' , '5' ], $row [ 'body' ][ 'list3' ], 'arrayRemove should remove the element' );
}
2025-09-03 15:57:03 +00:00
}