Skip to content

Commit edc731a

Browse files
committed
refactor Transaction.php for streaming transactions
1 parent 8ff842c commit edc731a

16 files changed

+1811
-267
lines changed

CHANGELOG.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,61 @@
11
Release notes for the ArangoDB-PHP driver 3.5.x
22
===============================================
33

4+
Added support for streaming transactions (i.e. transactions that can be composed of multiple
5+
operations on the client side piece-by-piece without specifying the full transaction operations
6+
in advance).
7+
8+
Streaming transactions currently support the following operations:
9+
10+
- fetch documents by id, i.e. `DocumentHandler::getById()`
11+
- update documents by id, i.e. `DocumentHandler::updateById()`
12+
- replace documents by id, i.e. `DocumentHandler::replaceById()`
13+
- remove documents by id, i.e. `DocumentHandler::removeById()`
14+
- insert documents, i.e. `DocumentHandler::insert()`
15+
- counting documents in a collection, i.e. `CollectionHandler::count()`
16+
- truncating a collection, i.e. `CollectionHandler::truncate()`
17+
- running AQL queries, i.e. `Statement::execute()`
18+
19+
Streaming transactions are provided by a new class `StreamingTransaction` and a new handler
20+
`StreamingTransactionHandler`.
21+
22+
$document = new DocumentHandler($connection);
23+
$transactionHandler = new StreamingTransactionHandler($connection);
24+
25+
// creates a transaction object
26+
$trx = new StreamingTransaction($connection, [
27+
TransactionBase::ENTRY_COLLECTIONS => [
28+
TransactionBase::ENTRY_WRITE => [ 'testCollection' ]
29+
]
30+
]);
31+
32+
// starts the transaction
33+
$trx = $transactionHandler->create($trx);
34+
35+
// get a StreamingTransactionCollection object. this is used to execute operations
36+
// in a transaction context
37+
$trxCollection = $trx->getCollection('testCollection');
38+
39+
// pass the StreamingTransactionCollection into the document operations instead of
40+
// a regular Collection object - this will make the operations execute in the context
41+
// of the currently running transaction
42+
$result = $documentHandler->insert($trxCollection, [ '_key' => 'test1', 'value' => 'test1' ]);
43+
44+
$result = $documentHandler->insert($trxCollection, [ '_key' => 'test2', 'value' => 'test2' ]);
45+
46+
// commits the transaction
47+
$transactionHandler->commit($trx);
48+
49+
Caveat: streaming transactions will normally stay open on the server side until they are explicitly
50+
aborted or committed by the client application, or until they time out automatically on the server.
51+
Therefore by default the PHP driver will automatically keep track of all begun streaming transactions,
52+
via an instance variable in the `StreamingTransactionHandler`.
53+
54+
Streaming transactions are automatically aborted on shutdown via a shutdown function, and all
55+
transactions started via `StreamingTransactionHandler` instances that were neither committed nor
56+
aborted by the user will be aborted.
57+
58+
459
The `CollectionHandler` class got a new method `createTtlIndex` for creating time-to-live (TTL)
560
indexes on the server.
661

examples/init.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
ConnectionOptions::OPTION_AUTH_PASSWD => '', // password for basic authorization
5252

5353
ConnectionOptions::OPTION_TIMEOUT => 30, // timeout in seconds
54-
ConnectionOptions::OPTION_TRACE => $traceFunc, // tracer function, can be used for debugging
54+
// ConnectionOptions::OPTION_TRACE => $traceFunc, // tracer function, can be used for debugging
5555
ConnectionOptions::OPTION_CREATE => false, // do not create unknown collections automatically
5656
ConnectionOptions::OPTION_UPDATE_POLICY => UpdatePolicy::LAST, // last update wins
5757
];

examples/transaction-query.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
namespace ArangoDBClient;
4+
5+
require __DIR__ . '/init.php';
6+
7+
try {
8+
$connection = new Connection($connectionOptions);
9+
$collectionHandler = new CollectionHandler($connection);
10+
$documentHandler = new DocumentHandler($connection);
11+
$transactionHandler = new StreamingTransactionHandler($connection);
12+
13+
// set up a document collection "users"
14+
$collection = new Collection('users');
15+
try {
16+
$collectionHandler->create($collection);
17+
} catch (\Exception $e) {
18+
// collection may already exist - ignore this error for now
19+
}
20+
21+
// clear everything, so we can start with a clean state
22+
$collectionHandler->truncate($collection);
23+
24+
// creates a transaction object
25+
$trx = new StreamingTransaction($connection, [
26+
TransactionBase::ENTRY_COLLECTIONS => [
27+
TransactionBase::ENTRY_WRITE => 'users'
28+
]
29+
]);
30+
31+
// starts the transaction
32+
$trx = $transactionHandler->create($trx);
33+
34+
// get a StreamingTransactionCollection object. this is used to execute operations
35+
// in a transaction context
36+
$trxCollection = $trx->getCollection('users');
37+
38+
// calling query() directly on the transaction makes the AQL query execute in the
39+
// context of the running transaction
40+
$result = $trx->query([
41+
'query' => 'FOR i IN 1..10 INSERT { _key: CONCAT("test", i), value: i } INTO @@collection',
42+
'bindVars' => [ '@collection' => $trxCollection->getName() ]
43+
]);
44+
45+
echo "BEFORE COMMIT" . PHP_EOL;
46+
echo "COLLECTION COUNT OUTSIDE OF TRANSACTION IS: ", $collectionHandler->count($collection) . PHP_EOL;
47+
echo "COLLECTION COUNT INSIDE OF TRANSACTION IS: ", $collectionHandler->count($trxCollection) . PHP_EOL;
48+
49+
// commits the transaction
50+
$transactionHandler->commit($trx);
51+
52+
echo PHP_EOL;
53+
echo "AFTER COMMIT" . PHP_EOL;
54+
echo "COLLECTION COUNT IS: ", $collectionHandler->count($collection) . PHP_EOL;
55+
} catch (ConnectException $e) {
56+
print $e . PHP_EOL;
57+
} catch (ServerException $e) {
58+
print $e . PHP_EOL;
59+
} catch (ClientException $e) {
60+
print $e . PHP_EOL;
61+
}

examples/transaction.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
namespace ArangoDBClient;
4+
5+
require __DIR__ . '/init.php';
6+
7+
try {
8+
$connection = new Connection($connectionOptions);
9+
$collectionHandler = new CollectionHandler($connection);
10+
$documentHandler = new DocumentHandler($connection);
11+
$transactionHandler = new StreamingTransactionHandler($connection);
12+
13+
// set up a document collection "users"
14+
$collection = new Collection('users');
15+
try {
16+
$collectionHandler->create($collection);
17+
} catch (\Exception $e) {
18+
// collection may already exist - ignore this error for now
19+
}
20+
21+
// clear everything, so we can start with a clean state
22+
$collectionHandler->truncate($collection);
23+
24+
// creates a transaction object
25+
$trx = new StreamingTransaction($connection, [
26+
TransactionBase::ENTRY_COLLECTIONS => [
27+
TransactionBase::ENTRY_WRITE => 'users'
28+
]
29+
]);
30+
31+
// starts the transaction
32+
$trx = $transactionHandler->create($trx);
33+
34+
// get a StreamingTransactionCollection object. this is used to execute operations
35+
// in a transaction context
36+
$trxCollection = $trx->getCollection('users');
37+
38+
// pass the StreamingTransactionCollection into the document operations instead of
39+
// a regular Collection object - this will make the operations execute in the context
40+
// of the currently running transaction
41+
$documentHandler->insert($trxCollection, [ '_key' => 'test1', 'value' => 'test1' ]);
42+
43+
$documentHandler->insert($trxCollection, [ '_key' => 'test2', 'value' => 'test2' ]);
44+
45+
echo "BEFORE COMMIT" . PHP_EOL;
46+
echo "COLLECTION COUNT OUTSIDE OF TRANSACTION IS: ", $collectionHandler->count($collection) . PHP_EOL;
47+
echo "COLLECTION COUNT INSIDE OF TRANSACTION IS: ", $collectionHandler->count($trxCollection) . PHP_EOL;
48+
49+
// commits the transaction
50+
$transactionHandler->commit($trx);
51+
52+
echo PHP_EOL;
53+
echo "AFTER COMMIT" . PHP_EOL;
54+
echo "COLLECTION COUNT IS: ", $collectionHandler->count($collection) . PHP_EOL;
55+
} catch (ConnectException $e) {
56+
print $e . PHP_EOL;
57+
} catch (ServerException $e) {
58+
print $e . PHP_EOL;
59+
} catch (ClientException $e) {
60+
print $e . PHP_EOL;
61+
}

lib/ArangoDBClient/CollectionHandler.php

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -393,9 +393,12 @@ public function has($collection)
393393
*/
394394
public function count($collection)
395395
{
396+
$headers = [];
397+
$this->addTransactionHeader($headers, $collection);
398+
396399
$collection = $this->makeCollection($collection);
397400
$url = UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collection, self::OPTION_COUNT]);
398-
$response = $this->getConnection()->get($url);
401+
$response = $this->getConnection()->get($url, $headers);
399402

400403
$data = $response->getJson();
401404
$count = $data[self::OPTION_COUNT];
@@ -616,15 +619,19 @@ public function unload($collection)
616619
*/
617620
public function truncate($collection)
618621
{
619-
$collectionId = $this->getCollectionId($collection);
620-
621-
if ($this->isValidCollectionId($collectionId)) {
622-
throw new ClientException('Cannot alter a collection without a collection id');
622+
$headers = [];
623+
$bodyParams = [];
624+
$this->addTransactionHeader($headers, $collection);
625+
if ($collection instanceof StreamingTransactionCollection) {
626+
$bodyParams['transactionId'] = $collection->getTrxId();
623627
}
624628

629+
$collection = $this->makeCollection($collection);
630+
625631
$this->getConnection()->put(
626-
UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collectionId, self::OPTION_TRUNCATE]),
627-
''
632+
UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collection, self::OPTION_TRUNCATE]),
633+
$this->json_encode_wrapper($bodyParams),
634+
$headers
628635
);
629636

630637
return true;

lib/ArangoDBClient/DocumentHandler.php

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -170,19 +170,21 @@ public function getById($collection, $documentId, array $options = [])
170170
*/
171171
protected function getDocument($url, $collection, $documentId, array $options = [])
172172
{
173-
$collection = $this->makeCollection($collection);
173+
$headers = [];
174+
$this->addTransactionHeader($headers, $collection);
175+
176+
$collection = $this->makeCollection($collection);
174177

175178
$url = UrlHelper::buildUrl($url, [$collection, $documentId]);
176-
$headerElements = [];
177179
if (array_key_exists('ifMatch', $options) && array_key_exists('revision', $options)) {
178180
if ($options['ifMatch'] === true) {
179-
$headerElements['If-Match'] = '"' . $options['revision'] . '"';
181+
$headers['If-Match'] = '"' . $options['revision'] . '"';
180182
} else {
181-
$headerElements['If-None-Match'] = '"' . $options['revision'] . '"';
183+
$headers['If-None-Match'] = '"' . $options['revision'] . '"';
182184
}
183185
}
184186

185-
$response = $this->getConnection()->get($url, $headerElements);
187+
$response = $this->getConnection()->get($url, $headers);
186188

187189
if ($response->getHttpCode() === 304) {
188190
throw new ClientException('Document has not changed.');
@@ -232,19 +234,21 @@ public function getHead($collection, $documentId, $revision = null, $ifMatch = n
232234
*/
233235
protected function head($url, $collection, $documentId, $revision = null, $ifMatch = null)
234236
{
235-
$collection = $this->makeCollection($collection);
237+
$headers = [];
238+
$this->addTransactionHeader($headers, $collection);
239+
240+
$collection = $this->makeCollection($collection);
236241

237242
$url = UrlHelper::buildUrl($url, [$collection, $documentId]);
238-
$headerElements = [];
239243
if ($revision !== null && $ifMatch !== null) {
240244
if ($ifMatch) {
241-
$headerElements['If-Match'] = '"' . $revision . '"';
245+
$headers['If-Match'] = '"' . $revision . '"';
242246
} else {
243-
$headerElements['If-None-Match'] = '"' . $revision . '"';
247+
$headers['If-None-Match'] = '"' . $revision . '"';
244248
}
245249
}
246-
247-
$response = $this->getConnection()->head($url, $headerElements);
250+
251+
$response = $this->getConnection()->head($url, $headers);
248252
$headers = $response->getHeaders();
249253
$headers['httpCode'] = $response->getHttpCode();
250254

@@ -337,6 +341,9 @@ public function store(Document $document, $collection = null, array $options = [
337341
*/
338342
public function save($collection, $document, array $options = [])
339343
{
344+
$headers = [];
345+
$this->addTransactionHeader($headers, $collection);
346+
340347
$collection = $this->makeCollection($collection);
341348
$_documentClass = $this->_documentClass;
342349

@@ -361,7 +368,7 @@ public function save($collection, $document, array $options = [])
361368
$data = $document->getAllForInsertUpdate();
362369
}
363370

364-
$response = $this->getConnection()->post($url, $this->json_encode_wrapper($data));
371+
$response = $this->getConnection()->post($url, $this->json_encode_wrapper($data), $headers);
365372
$json = $response->getJson();
366373

367374
// This makes sure that if we're in batch mode, it will not go further and choke on the checks below.
@@ -493,6 +500,9 @@ public function updateById($collection, $documentId, Document $document, array $
493500
*/
494501
protected function patch($url, $collection, $documentId, Document $document, array $options = [])
495502
{
503+
$headers = [];
504+
$this->addTransactionHeader($headers, $collection);
505+
496506
$collection = $this->makeCollection($collection);
497507
$_documentClass = $this->_documentClass;
498508

@@ -509,7 +519,6 @@ protected function patch($url, $collection, $documentId, Document $document, arr
509519
);
510520

511521

512-
$headers = [];
513522
if (isset($params[ConnectionOptions::OPTION_UPDATE_POLICY]) &&
514523
$params[ConnectionOptions::OPTION_UPDATE_POLICY] === UpdatePolicy::ERROR
515524
) {
@@ -618,6 +627,9 @@ public function replaceById($collection, $documentId, Document $document, array
618627
*/
619628
protected function put($url, $collection, $documentId, Document $document, array $options = [])
620629
{
630+
$headers = [];
631+
$this->addTransactionHeader($headers, $collection);
632+
621633
$collection = $this->makeCollection($collection);
622634
$_documentClass = $this->_documentClass;
623635

@@ -632,7 +644,6 @@ protected function put($url, $collection, $documentId, Document $document, array
632644
]
633645
);
634646

635-
$headers = [];
636647
if (isset($params[ConnectionOptions::OPTION_REPLACE_POLICY]) &&
637648
$params[ConnectionOptions::OPTION_REPLACE_POLICY] === UpdatePolicy::ERROR
638649
) {
@@ -641,7 +652,7 @@ protected function put($url, $collection, $documentId, Document $document, array
641652
$headers['if-match'] = '"' . $options['revision'] . '"';
642653
}
643654
}
644-
655+
645656
$data = $document->getAllForInsertUpdate();
646657

647658
$url = UrlHelper::buildUrl($url, [$collection, $documentId]);
@@ -725,6 +736,9 @@ public function removeById($collection, $documentId, $revision = null, array $op
725736
*/
726737
protected function erase($url, $collection, $documentId, $revision = null, array $options = [])
727738
{
739+
$headers = [];
740+
$this->addTransactionHeader($headers, $collection);
741+
728742
$collection = $this->makeCollection($collection);
729743

730744
$params = $this->includeOptionsInParams(
@@ -737,7 +751,6 @@ protected function erase($url, $collection, $documentId, $revision = null, array
737751
]
738752
);
739753

740-
$headers = [];
741754
if (isset($params[ConnectionOptions::OPTION_DELETE_POLICY]) &&
742755
$params[ConnectionOptions::OPTION_DELETE_POLICY] === UpdatePolicy::ERROR
743756
) {

lib/ArangoDBClient/Handler.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,19 @@ protected function makeCollection($value)
174174
return $value;
175175
}
176176

177+
178+
/**
179+
* Add a transaction header to the array of headers in case this is a transactional operation
180+
*
181+
* @param array $headers - already existing headers
182+
* @param mixed $collection - any type of collection (can be StreamingTransactionCollection or other)
183+
*/
184+
protected function addTransactionHeader(array &$headers, $collection)
185+
{
186+
if ($collection instanceof StreamingTransactionCollection) {
187+
$headers['x-arango-trx-id'] = $collection->getTrxId();
188+
}
189+
}
177190
}
178191

179192
class_alias(Handler::class, '\triagens\ArangoDb\Handler');

0 commit comments

Comments
 (0)