2025-09-02 15:53:08 +00:00
< ? php
2025-09-11 04:37:43 +00:00
namespace Tests\E2E\Services\Databases\TablesDB\Transactions ;
2025-09-02 15:53:08 +00:00
use Tests\E2E\Client ;
use Tests\E2E\Scopes\ProjectCustom ;
use Tests\E2E\Scopes\Scope ;
use Tests\E2E\Scopes\SideClient ;
use Utopia\Database\Database ;
use Utopia\Database\Helpers\ID ;
use Utopia\Database\Helpers\Permission ;
use Utopia\Database\Helpers\Role ;
2025-09-03 15:57:03 +00:00
class ACIDTest extends Scope
2025-09-02 15:53:08 +00:00
{
use ProjectCustom ;
use SideClient ;
/**
* Test atomicity - all operations succeed or all fail
*/
2025-09-03 15:57:03 +00:00
public function testAtomicity () : void
2025-09-02 15:53:08 +00:00
{
// Create database
2025-09-08 12:50:48 +00:00
$database = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'AtomicityTestDB'
]);
$this -> assertEquals ( 201 , $database [ 'headers' ][ 'status-code' ]);
$databaseId = $database [ 'body' ][ '$id' ];
2025-09-11 05:54:55 +00:00
// Create table with unique constraint
$table = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2025-09-11 05:54:55 +00:00
'tableId' => ID :: unique (),
2025-09-02 15:53:08 +00:00
'name' => 'AtomicityTest' ,
2025-09-11 05:54:55 +00:00
'rowSecurity' => false ,
2025-09-02 15:53:08 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
],
]);
2025-09-11 05:54:55 +00:00
$tableId = $table [ 'body' ][ '$id' ];
2025-09-02 15:53:08 +00:00
2025-09-08 12:50:48 +00:00
// Add unique column
2025-09-11 05:54:55 +00:00
$this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'email' ,
'size' => 256 ,
'required' => true ,
]);
// Add unique index
2025-09-11 05:54:55 +00:00
$this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/indexes' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'unique_email' ,
'type' => Database :: INDEX_UNIQUE ,
2025-09-08 12:50:48 +00:00
'columns' => [ 'email' ]
2025-09-02 15:53:08 +00:00
]);
sleep ( 3 );
2025-09-11 05:54:55 +00:00
// Create first row outside transaction
$doc1 = $this -> client -> call ( Client :: METHOD_POST , " /tablesdb/ { $databaseId } /tables/ { $tableId } /rows " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
2025-09-11 05:54:55 +00:00
'rowId' => ID :: unique (),
2025-09-02 15:53:08 +00:00
'data' => [
'email' => 'existing@example.com'
]
]);
$this -> assertEquals ( 201 , $doc1 [ 'headers' ][ 'status-code' ]);
// Create transaction
2025-09-08 12:50:48 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/transactions' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]));
$this -> assertEquals ( 201 , $transaction [ 'headers' ][ 'status-code' ], 'Transaction creation should succeed. Response: ' . json_encode ( $transaction ));
$this -> assertArrayHasKey ( '$id' , $transaction [ 'body' ], 'Transaction response should have $id. Response body: ' . json_encode ( $transaction [ 'body' ]));
$transactionId = $transaction [ 'body' ][ '$id' ];
// Add operations - second one will fail due to unique constraint
2025-09-08 12:50:48 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , " /tablesdb/transactions/ { $transactionId } /operations " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2025-09-11 05:54:55 +00:00
'tableId' => $tableId ,
2025-09-02 15:53:08 +00:00
'action' => 'create' ,
2025-09-11 05:54:55 +00:00
'rowId' => ID :: unique (),
2025-09-02 15:53:08 +00:00
'data' => [
'email' => 'newuser@example.com' // This should succeed
]
],
[
'databaseId' => $databaseId ,
2025-09-11 05:54:55 +00:00
'tableId' => $tableId ,
2025-09-02 15:53:08 +00:00
'action' => 'create' ,
2025-09-11 05:54:55 +00:00
'rowId' => ID :: unique (),
2025-09-02 15:53:08 +00:00
'data' => [
'email' => 'existing@example.com' // This will fail - duplicate
]
],
[
'databaseId' => $databaseId ,
2025-09-11 05:54:55 +00:00
'tableId' => $tableId ,
2025-09-02 15:53:08 +00:00
'action' => 'create' ,
2025-09-11 05:54:55 +00:00
'rowId' => ID :: unique (),
2025-09-02 15:53:08 +00:00
'data' => [
'email' => 'anotheruser@example.com' // This should not be created due to atomicity
]
]
]
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ], 'Add operations failed. Response: ' . json_encode ( $response [ 'body' ]));
// Attempt to commit - should fail due to unique constraint violation
2025-09-08 12:50:48 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , " /tablesdb/transactions/ { $transactionId } " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
if ( $response [ 'headers' ][ 'status-code' ] === 200 ) {
2025-09-11 05:54:55 +00:00
// If transaction succeeded, all rows should be created
$rows = $this -> client -> call ( Client :: METHOD_GET , " /tablesdb/ { $databaseId } /tables/ { $tableId } /rows " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2025-09-11 05:54:55 +00:00
// Should have 4 rows total (1 original + 3 from transaction)
2025-09-02 15:53:08 +00:00
// But since we have a unique constraint violation, this might fail
2025-09-11 05:54:55 +00:00
$this -> assertGreaterThanOrEqual ( 1 , $rows [ 'body' ][ 'total' ]);
2025-09-02 15:53:08 +00:00
} else {
$this -> assertEquals ( 409 , $response [ 'headers' ][ 'status-code' ]); // Conflict error
2025-09-02 16:29:42 +00:00
2025-09-11 05:54:55 +00:00
// Verify NO new rows were created (atomicity)
$rows = $this -> client -> call ( Client :: METHOD_GET , " /tablesdb/ { $databaseId } /tables/ { $tableId } /rows " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2025-09-11 05:54:55 +00:00
$this -> assertEquals ( 1 , $rows [ 'body' ][ 'total' ]); // Only the original row
$this -> assertEquals ( 'existing@example.com' , $rows [ 'body' ][ 'rows' ][ 0 ][ 'email' ]);
2025-09-02 15:53:08 +00:00
}
}
/**
* Test consistency - schema validation and constraints
*/
2025-09-03 15:57:03 +00:00
public function testConsistency () : void
2025-09-02 15:53:08 +00:00
{
// Create database
2025-09-08 12:50:48 +00:00
$database = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'ConsistencyTestDB'
]);
$this -> assertEquals ( 201 , $database [ 'headers' ][ 'status-code' ]);
$databaseId = $database [ 'body' ][ '$id' ];
2025-09-11 05:54:55 +00:00
// Create table with required fields and constraints
$table = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2025-09-11 05:54:55 +00:00
'tableId' => ID :: unique (),
2025-09-02 15:53:08 +00:00
'name' => 'ConsistencyTest' ,
2025-09-11 05:54:55 +00:00
'rowSecurity' => false ,
2025-09-02 15:53:08 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
],
]);
2025-09-11 05:54:55 +00:00
$tableId = $table [ 'body' ][ '$id' ];
2025-09-02 15:53:08 +00:00
2025-09-08 12:50:48 +00:00
// Add required string column
2025-09-11 05:54:55 +00:00
$this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'required_field' ,
'size' => 256 ,
'required' => true ,
]);
2025-09-08 12:50:48 +00:00
// Add integer column with min/max constraints
2025-09-11 05:54:55 +00:00
$this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/integer' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'age' ,
'required' => true ,
'min' => 18 ,
'max' => 100
]);
sleep ( 3 );
// Create transaction
2025-09-08 12:50:48 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/transactions' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]));
$transactionId = $transaction [ 'body' ][ '$id' ];
// Add operations with both valid and invalid data
2025-09-08 12:50:48 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , " /tablesdb/transactions/ { $transactionId } /operations " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2025-09-11 05:54:55 +00:00
'tableId' => $tableId ,
2025-09-02 15:53:08 +00:00
'action' => 'create' ,
2025-09-11 05:54:55 +00:00
'rowId' => ID :: unique (),
2025-09-02 15:53:08 +00:00
'data' => [
'required_field' => 'Valid User' ,
'age' => 25 // Valid age
]
],
[
'databaseId' => $databaseId ,
2025-09-11 05:54:55 +00:00
'tableId' => $tableId ,
2025-09-02 15:53:08 +00:00
'action' => 'create' ,
2025-09-11 05:54:55 +00:00
'rowId' => ID :: unique (),
2025-09-02 15:53:08 +00:00
'data' => [
'required_field' => 'Too Young User' ,
'age' => 10 // Below minimum - will fail constraint
]
],
[
'databaseId' => $databaseId ,
2025-09-11 05:54:55 +00:00
'tableId' => $tableId ,
2025-09-02 15:53:08 +00:00
'action' => 'create' ,
2025-09-11 05:54:55 +00:00
'rowId' => ID :: unique (),
2025-09-02 15:53:08 +00:00
'data' => [
'required_field' => 'Another Valid User' ,
'age' => 30 // Valid but should not be created due to transaction failure
]
]
]
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
// Attempt to commit - should fail due to constraint violation
2025-09-08 12:50:48 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , " /tablesdb/transactions/ { $transactionId } " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
$this -> assertContains ( $response [ 'headers' ][ 'status-code' ], [ 400 , 500 ], 'Transaction commit should fail due to validation. Response: ' . json_encode ( $response [ 'body' ]));
2025-09-11 05:54:55 +00:00
// Verify no rows were created
$rows = $this -> client -> call ( Client :: METHOD_GET , " /tablesdb/ { $databaseId } /tables/ { $tableId } /rows " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2025-09-11 05:54:55 +00:00
$this -> assertEquals ( 0 , $rows [ 'body' ][ 'total' ]);
2025-09-02 15:53:08 +00:00
}
/**
* Test isolation - concurrent transactions on same data
*/
2025-09-03 15:57:03 +00:00
public function testIsolation () : void
2025-09-02 15:53:08 +00:00
{
// Create database
2025-09-08 12:50:48 +00:00
$database = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'IsolationTestDB'
]);
$this -> assertEquals ( 201 , $database [ 'headers' ][ 'status-code' ]);
$databaseId = $database [ 'body' ][ '$id' ];
2025-09-11 05:54:55 +00:00
// Create table
$table = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2025-09-11 05:54:55 +00:00
'tableId' => ID :: unique (),
2025-09-02 15:53:08 +00:00
'name' => 'IsolationTest' ,
2025-09-11 05:54:55 +00:00
'rowSecurity' => false ,
2025-09-02 15:53:08 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
],
]);
2025-09-11 05:54:55 +00:00
$tableId = $table [ 'body' ][ '$id' ];
2025-09-02 15:53:08 +00:00
2025-09-08 12:50:48 +00:00
// Add counter column
2025-09-11 05:54:55 +00:00
$this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/integer' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'counter' ,
'required' => true ,
'min' => 0 ,
'max' => 1000000
]);
sleep ( 2 );
2025-09-11 05:54:55 +00:00
// Create initial row with counter
$doc = $this -> client -> call ( Client :: METHOD_POST , " /tablesdb/ { $databaseId } /tables/ { $tableId } /rows " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
2025-09-11 05:54:55 +00:00
'rowId' => 'shared_counter' ,
2025-09-02 15:53:08 +00:00
'data' => [
'counter' => 0
]
]);
$this -> assertEquals ( 201 , $doc [ 'headers' ][ 'status-code' ]);
// Create first transaction
2025-09-08 12:50:48 +00:00
$transaction1 = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/transactions' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]));
$this -> assertEquals ( 201 , $transaction1 [ 'headers' ][ 'status-code' ], 'Transaction 1 creation should succeed' );
$this -> assertArrayHasKey ( '$id' , $transaction1 [ 'body' ], 'Transaction 1 response should have $id' );
$transactionId1 = $transaction1 [ 'body' ][ '$id' ];
// Create second transaction
2025-09-08 12:50:48 +00:00
$transaction2 = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/transactions' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]));
$this -> assertEquals ( 201 , $transaction2 [ 'headers' ][ 'status-code' ], 'Transaction 2 creation should succeed' );
$this -> assertArrayHasKey ( '$id' , $transaction2 [ 'body' ], 'Transaction 2 response should have $id' );
$transactionId2 = $transaction2 [ 'body' ][ '$id' ];
// Transaction 1: Increment counter by 10
2025-09-08 12:50:48 +00:00
$this -> client -> call ( Client :: METHOD_POST , " /tablesdb/transactions/ { $transactionId1 } /operations " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2025-09-11 05:54:55 +00:00
'tableId' => $tableId ,
'rowId' => 'shared_counter' ,
2025-09-02 15:53:08 +00:00
'action' => 'increment' ,
'data' => [
2025-09-08 12:50:48 +00:00
'column' => 'counter' ,
2025-09-02 15:53:08 +00:00
'value' => 10
]
]
]
]);
// Transaction 2: Increment counter by 5
2025-09-08 12:50:48 +00:00
$this -> client -> call ( Client :: METHOD_POST , " /tablesdb/transactions/ { $transactionId2 } /operations " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2025-09-11 05:54:55 +00:00
'tableId' => $tableId ,
'rowId' => 'shared_counter' ,
2025-09-02 15:53:08 +00:00
'action' => 'increment' ,
'data' => [
2025-09-08 12:50:48 +00:00
'column' => 'counter' ,
2025-09-02 15:53:08 +00:00
'value' => 5
]
]
]
]);
// Commit first transaction
2025-09-08 12:50:48 +00:00
$response1 = $this -> client -> call ( Client :: METHOD_PATCH , " /tablesdb/transactions/ { $transactionId1 } " , array_merge ([
2025-09-02 15:53:08 +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
2025-09-08 12:50:48 +00:00
$response2 = $this -> client -> call ( Client :: METHOD_PATCH , " /tablesdb/transactions/ { $transactionId2 } " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'commit' => true
]);
$this -> assertEquals ( 200 , $response2 [ 'headers' ][ 'status-code' ]);
// Check final value - both increments should be applied
2025-09-11 05:54:55 +00:00
$row = $this -> client -> call ( Client :: METHOD_GET , " /tablesdb/ { $databaseId } /tables/ { $tableId } /rows/shared_counter " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
// Both increments should be applied: 0 + 10 + 5 = 15
2025-09-11 05:54:55 +00:00
$this -> assertEquals ( 15 , $row [ 'body' ][ 'counter' ]);
2025-09-02 15:53:08 +00:00
}
/**
* Test durability - committed data persists
*/
2025-09-03 15:57:03 +00:00
public function testDurability () : void
2025-09-02 15:53:08 +00:00
{
// Create database
2025-09-08 12:50:48 +00:00
$database = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'DurabilityTestDB'
]);
$this -> assertEquals ( 201 , $database [ 'headers' ][ 'status-code' ]);
$databaseId = $database [ 'body' ][ '$id' ];
2025-09-11 05:54:55 +00:00
// Create table
$table = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2025-09-11 05:54:55 +00:00
'tableId' => ID :: unique (),
2025-09-02 15:53:08 +00:00
'name' => 'DurabilityTest' ,
2025-09-11 05:54:55 +00:00
'rowSecurity' => false ,
2025-09-02 15:53:08 +00:00
'permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
2025-09-11 05:54:55 +00:00
$tableId = $table [ 'body' ][ '$id' ];
2025-09-02 15:53:08 +00:00
2025-09-08 12:50:48 +00:00
// Add column
2025-09-11 05:54:55 +00:00
$this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'data' ,
'size' => 256 ,
'required' => true ,
]);
sleep ( 2 );
// Create and commit transaction with multiple operations
2025-09-08 12:50:48 +00:00
$transaction = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/transactions' , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]));
$this -> assertEquals ( 201 , $transaction [ 'headers' ][ 'status-code' ], 'Transaction creation should succeed' );
$this -> assertArrayHasKey ( '$id' , $transaction [ 'body' ], 'Transaction response should have $id' );
$transactionId = $transaction [ 'body' ][ '$id' ];
// Add multiple operations
2025-09-08 12:50:48 +00:00
$this -> client -> call ( Client :: METHOD_POST , " /tablesdb/transactions/ { $transactionId } /operations " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'operations' => [
[
'databaseId' => $databaseId ,
2025-09-11 05:54:55 +00:00
'tableId' => $tableId ,
2025-09-02 15:53:08 +00:00
'action' => 'create' ,
2025-09-11 05:54:55 +00:00
'rowId' => 'durable_doc_1' ,
2025-09-02 15:53:08 +00:00
'data' => [
'data' => 'Important data 1'
]
],
[
'databaseId' => $databaseId ,
2025-09-11 05:54:55 +00:00
'tableId' => $tableId ,
2025-09-02 15:53:08 +00:00
'action' => 'create' ,
2025-09-11 05:54:55 +00:00
'rowId' => 'durable_doc_2' ,
2025-09-02 15:53:08 +00:00
'data' => [
'data' => 'Important data 2'
]
],
[
'databaseId' => $databaseId ,
2025-09-11 05:54:55 +00:00
'tableId' => $tableId ,
2025-09-02 15:53:08 +00:00
'action' => 'update' ,
2025-09-11 05:54:55 +00:00
'rowId' => 'durable_doc_1' ,
2025-09-02 15:53:08 +00:00
'data' => [
'data' => 'Updated important data 1'
]
]
]
]);
// Commit transaction
2025-09-08 12:50:48 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , " /tablesdb/transactions/ { $transactionId } " , array_merge ([
2025-09-02 15:53:08 +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' ], 'Commit should succeed. Response: ' . json_encode ( $response [ 'body' ]));
$this -> assertEquals ( 'committed' , $response [ 'body' ][ 'status' ]);
2025-09-11 05:54:55 +00:00
// List all rows to see what was created
$allDocs = $this -> client -> call ( Client :: METHOD_GET , " /tablesdb/ { $databaseId } /tables/ { $tableId } /rows " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2025-09-02 16:29:42 +00:00
2025-09-11 05:54:55 +00:00
$this -> assertGreaterThan ( 0 , $allDocs [ 'body' ][ 'total' ], 'Should have created rows. Found: ' . json_encode ( $allDocs [ 'body' ]));
2025-09-02 15:53:08 +00:00
2025-09-11 05:54:55 +00:00
// Verify rows exist and have correct data
$row1 = $this -> client -> call ( Client :: METHOD_GET , " /tablesdb/ { $databaseId } /tables/ { $tableId } /rows/durable_doc_1 " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2025-09-11 05:54:55 +00:00
$this -> assertEquals ( 200 , $row1 [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'Updated important data 1' , $row1 [ 'body' ][ 'data' ]);
2025-09-02 15:53:08 +00:00
2025-09-11 05:54:55 +00:00
$row2 = $this -> client -> call ( Client :: METHOD_GET , " /tablesdb/ { $databaseId } /tables/ { $tableId } /rows/durable_doc_2 " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2025-09-11 05:54:55 +00:00
$this -> assertEquals ( 200 , $row2 [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'Important data 2' , $row2 [ 'body' ][ 'data' ]);
2025-09-02 15:53:08 +00:00
// Further update outside transaction to ensure persistence
2025-09-11 05:54:55 +00:00
$update = $this -> client -> call ( Client :: METHOD_PATCH , " /tablesdb/ { $databaseId } /tables/ { $tableId } /rows/durable_doc_1 " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'data' => [
'data' => 'Modified outside transaction'
]
]);
$this -> assertEquals ( 200 , $update [ 'headers' ][ 'status-code' ]);
// Verify the update persisted
2025-09-11 05:54:55 +00:00
$row1 = $this -> client -> call ( Client :: METHOD_GET , " /tablesdb/ { $databaseId } /tables/ { $tableId } /rows/durable_doc_1 " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2025-09-11 05:54:55 +00:00
$this -> assertEquals ( 'Modified outside transaction' , $row1 [ 'body' ][ 'data' ]);
2025-09-02 15:53:08 +00:00
2025-09-11 05:54:55 +00:00
// List all rows to verify total count
$rows = $this -> client -> call ( Client :: METHOD_GET , " /tablesdb/ { $databaseId } /tables/ { $tableId } /rows " , array_merge ([
2025-09-02 15:53:08 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2025-09-11 05:54:55 +00:00
$this -> assertEquals ( 2 , $rows [ 'body' ][ 'total' ]);
2025-09-02 15:53:08 +00:00
}
2025-09-02 16:29:42 +00:00
}