From 7e3124ce1194fd9571f2bac3e938ce10f261e354 Mon Sep 17 00:00:00 2001
From: Ivan Ignatiev
Date: Sun, 17 Mar 2019 10:21:16 +0100
Subject: [PATCH 001/101] Add non-existing options keys to return parameters in
Handler::includeOptionsInParams function
---
lib/ArangoDBClient/Handler.php | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/lib/ArangoDBClient/Handler.php b/lib/ArangoDBClient/Handler.php
index 965365fe..c1321e69 100644
--- a/lib/ArangoDBClient/Handler.php
+++ b/lib/ArangoDBClient/Handler.php
@@ -111,6 +111,18 @@ protected function includeOptionsInParams($options, array $includeArray = [])
}
}
+ foreach ($includeArray as $key => $value) {
+ if (!array_key_exists($key, $options)) {
+ if ($key === ConnectionOptions::OPTION_UPDATE_POLICY) {
+ UpdatePolicy::validate($value);
+ }
+
+ if ($value !== null) {
+ $params[$key] = $value;
+ }
+ }
+ }
+
return $params;
}
From 9a7dfea5908aab8195797f45513792d6f11fbee9 Mon Sep 17 00:00:00 2001
From: Ivan Ignatiev
Date: Sun, 17 Mar 2019 11:00:18 +0100
Subject: [PATCH 002/101] Fix collection creation options if trying insert new
document in non-existing collection
---
lib/ArangoDBClient/DocumentHandler.php | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/lib/ArangoDBClient/DocumentHandler.php b/lib/ArangoDBClient/DocumentHandler.php
index e189dc82..23fdf5f3 100644
--- a/lib/ArangoDBClient/DocumentHandler.php
+++ b/lib/ArangoDBClient/DocumentHandler.php
@@ -806,6 +806,7 @@ private function getRevision($document)
return $revision;
}
+
/**
* @param $collection mixed collection name or id
* @param array $options - optional, array of options
@@ -813,10 +814,12 @@ private function getRevision($document)
*
'createCollection' - true to create the collection if it does not exist
*
'createCollectionType' - "document" or 2 for document collection
*
"edge" or 3 for edge collection
+ *
'waitForSync' - if set to true, then all removal operations will instantly be synchronised to disk / If this is not specified, then the collection's default sync behavior will be applied.
*
*/
protected function createCollectionIfOptions($collection, $options)
{
+
if (!array_key_exists(CollectionHandler::OPTION_CREATE_COLLECTION, $options)) {
return;
}
@@ -829,18 +832,25 @@ protected function createCollectionIfOptions($collection, $options)
$collectionHandler = new CollectionHandler($this->getConnection());
+ $params = [];
+
if (array_key_exists('createCollectionType', $options)) {
- $options['type'] = $options['createCollectionType'];
- unset($options['createCollectionType']);
+ $params['type'] = $options['createCollectionType'];
+ }
+
+ if (array_key_exists('waitForSync', $options)) {
+ $params['waitForSync'] = $options['waitForSync'];
}
- unset($options['createCollection']);
+
try {
// attempt to create the collection
- $collectionHandler->create($collection, $options);
+ $collectionHandler->create($collection, $params);
} catch (Exception $e) {
// collection may have existed already
}
}
}
+}
+
class_alias(DocumentHandler::class, '\triagens\ArangoDb\DocumentHandler');
From f7a58b46067db50d8c6c27c8bc4907b5551d5ac1 Mon Sep 17 00:00:00 2001
From: Ivan Ignatiev
Date: Sun, 17 Mar 2019 11:18:13 +0100
Subject: [PATCH 003/101] Add test for inserting a new object without created
collection and with connection OPTION_CREATE = true
---
tests/DocumentBasicTest.php | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/tests/DocumentBasicTest.php b/tests/DocumentBasicTest.php
index b921bea5..c92dd64a 100644
--- a/tests/DocumentBasicTest.php
+++ b/tests/DocumentBasicTest.php
@@ -197,6 +197,41 @@ public function testCreateAndDeleteDocumentWithoutCreatedCollection()
}
+ /**
+ * Try to create and delete a document with OPTION_CREATE = true
+ */
+ public function testCreateAndDeleteDocumentWithoutCreatedCollectionAndOptionCreate()
+ {
+ $connection = $this->connection;
+ $document = new Document();
+ $documentHandler = new DocumentHandler($connection);
+
+ $options = $connection->getOptions();
+ $connection->setOption(ConnectionOptions::OPTION_CREATE, true);
+
+ try {
+ $this->collectionHandler->drop('ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp);
+ } catch (\Exception $e) {
+ // don't bother us, if it's already deleted.
+ }
+
+ $document->someAttribute = 'someValue';
+
+ $documentId = $documentHandler->save('ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp, $document);
+
+ $resultingDocument = $documentHandler->get('ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp, $documentId);
+
+ $resultingAttribute = $resultingDocument->someAttribute;
+ static::assertSame('someValue', $resultingAttribute, 'Resulting Attribute should be "someValue". It\'s :' . $resultingAttribute);
+
+ $documentHandler->remove($document);
+
+ $connection->setOption(ConnectionOptions::OPTION_CREATE, $options[ConnectionOptions::OPTION_CREATE]);
+
+ }
+
+
+
/**
* Try to create and delete a document using a defined key
*/
From a9fd568df42a9d3aa8e239d54950a3b3f0b85d81 Mon Sep 17 00:00:00 2001
From: Ivan Ignatiev
Date: Sun, 17 Mar 2019 11:24:36 +0100
Subject: [PATCH 004/101] Fix typo
---
lib/ArangoDBClient/DocumentHandler.php | 1 -
1 file changed, 1 deletion(-)
diff --git a/lib/ArangoDBClient/DocumentHandler.php b/lib/ArangoDBClient/DocumentHandler.php
index 23fdf5f3..e2b05fd4 100644
--- a/lib/ArangoDBClient/DocumentHandler.php
+++ b/lib/ArangoDBClient/DocumentHandler.php
@@ -849,7 +849,6 @@ protected function createCollectionIfOptions($collection, $options)
// collection may have existed already
}
}
-}
}
From 7a773c195e2dd8c5328753373ce384e77f2b4565 Mon Sep 17 00:00:00 2001
From: Sony AK
Date: Thu, 21 Mar 2019 10:04:17 +0700
Subject: [PATCH 005/101] Update README.md
---
docs/Drivers/PHP/Tutorial/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/Drivers/PHP/Tutorial/README.md b/docs/Drivers/PHP/Tutorial/README.md
index bb9b06ec..ac8a1aa7 100644
--- a/docs/Drivers/PHP/Tutorial/README.md
+++ b/docs/Drivers/PHP/Tutorial/README.md
@@ -277,7 +277,7 @@ object(ArangoDBClient\Document)##6 (4) {
*/
```
-Whenever the document id is yet unknown, but you want to fetch a document from the server by any of its other properties, you can use the CollectionHandler->byExample() method. It allows you to provide an example of the document that you are looking for. The example should either be a Document object with the relevant properties set, or, a PHP array with the propeties that you are looking for:
+Whenever the document id is yet unknown, but you want to fetch a document from the server by any of its other properties, you can use the CollectionHandler->byExample() method. It allows you to provide an example of the document that you are looking for. The example should either be a Document object with the relevant properties set, or, a PHP array with the properties that you are looking for:
```php
// get a document list back from the server, using a document example
From 8887998de2776db5c70c3d153fec46f15bb5079b Mon Sep 17 00:00:00 2001
From: Sony AK
Date: Tue, 26 Mar 2019 16:39:09 +0700
Subject: [PATCH 006/101] small typo :)
---
docs/Drivers/PHP/Tutorial/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/Drivers/PHP/Tutorial/README.md b/docs/Drivers/PHP/Tutorial/README.md
index ac8a1aa7..b0aae139 100644
--- a/docs/Drivers/PHP/Tutorial/README.md
+++ b/docs/Drivers/PHP/Tutorial/README.md
@@ -287,7 +287,7 @@ Whenever the document id is yet unknown, but you want to fetch a document from t
```
This will return all documents from the specified collection (here: "users") with the properties provided in the example (here: that have an attribute "name" with a value of "John"). The result is a cursor which can be iterated sequentially or completely. We have chosen to get the complete result set above by calling the cursor's getAll() method.
-Note that CollectionHandler->byExample() might return multiple documents if the example is ambigious.
+Note that CollectionHandler->byExample() might return multiple documents if the example is ambiguous.
## Updating a document
From 620954452fbdcfaa5702c5dd3a0385149054143c Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Wed, 27 Mar 2019 17:31:26 +0100
Subject: [PATCH 007/101] fix a test
---
tests/AdminTest.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/AdminTest.php b/tests/AdminTest.php
index 7ffd33fc..d2e8a4cc 100644
--- a/tests/AdminTest.php
+++ b/tests/AdminTest.php
@@ -50,7 +50,7 @@ public function testGetServerVersionWithDetails()
$details = $result['details'];
static::assertArrayHasKey('build-date', $details);
static::assertArrayHasKey('icu-version', $details);
- static::assertArrayHasKey('openssl-version', $details);
+ static::assertArrayHasKey('openssl-version-compile-time', $details);
static::assertArrayHasKey('server-version', $details);
static::assertArrayHasKey('v8-version', $details);
}
From 24c47abc49d23416b1b80af56b7d380d83153e77 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Fri, 28 Jun 2019 16:51:51 +0200
Subject: [PATCH 008/101] make it work with current devel version
---
tests/GraphExtendedTest.php | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/tests/GraphExtendedTest.php b/tests/GraphExtendedTest.php
index 75987d58..dec6b82e 100644
--- a/tests/GraphExtendedTest.php
+++ b/tests/GraphExtendedTest.php
@@ -575,7 +575,7 @@ public function testGetReplaceUpdateAndRemoveOnNonExistentObjects()
// Try to replace edge using GraphHandler
- // This should cause an exception with a code of 404
+ // This should cause an exception with a code of 400
try {
$e = null;
$this->graphHandler->replaceEdge($this->graphName, $this->edge1Name, 'label', $edge1);
@@ -583,7 +583,7 @@ public function testGetReplaceUpdateAndRemoveOnNonExistentObjects()
// don't bother us... just give us the $e
}
static::assertInstanceOf(ServerException::class, $e);
- static::assertEquals(404, $e->getCode(), 'Should be 404, instead got: ' . $e->getCode());
+ static::assertEquals(400, $e->getCode(), 'Should be 400, instead got: ' . $e->getCode());
// Try to remove the edge using GraphHandler
@@ -801,18 +801,18 @@ public function testSaveVerticesAndSaveReplaceUpdateAndRemoveEdge()
static::assertEquals('vertex2', $result2->getKey(), 'Did not return vertex2!');
- $result1 = $this->graphHandler->saveEdge(
+ $result3 = $this->graphHandler->saveEdge(
$this->graphName,
$result1->getInternalId(),
$result2->getInternalId(),
$this->edgeLabel1,
$edge1
);
- static::assertEquals($this->edgeCollectionName . '/edge1', $result1, 'Did not return edge1!');
+ static::assertEquals($this->edgeCollectionName . '/edge1', $result3, 'Did not return edge1!');
- $result1 = $this->graphHandler->getEdge($this->graphName, $this->edge1Name);
- static::assertEquals('edge1', $result1->getKey(), 'Did not return edge1!');
+ $result3 = $this->graphHandler->getEdge($this->graphName, $this->edge1Name);
+ static::assertEquals('edge1', $result3->getKey(), 'Did not return edge1!');
$edge1a->setFrom($result1->getInternalId());
$edge1a->setTo($result2->getInternalId());
@@ -898,18 +898,18 @@ public function testSaveVerticesAndConditionalSaveReplaceUpdateAndRemoveEdge()
static::assertEquals('vertex2', $result2->getKey(), 'Did not return vertex2!');
- $result1 = $this->graphHandler->saveEdge(
+ $result3 = $this->graphHandler->saveEdge(
$this->graphName,
$result1->getInternalId(),
$result2->getInternalId(),
$this->edgeLabel1,
$edge1
);
- static::assertEquals('ArangoDB_PHP_TestSuite_TestEdgeCollection_01' . '_' . static::$testsTimestamp . '/edge1', $result1, 'Did not return edge1!');
+ static::assertEquals('ArangoDB_PHP_TestSuite_TestEdgeCollection_01' . '_' . static::$testsTimestamp . '/edge1', $result3, 'Did not return edge1!');
- $result1 = $this->graphHandler->getEdge($this->graphName, $this->edge1Name);
- static::assertEquals('edge1', $result1->getKey(), 'Did not return edge1!');
+ $result3 = $this->graphHandler->getEdge($this->graphName, $this->edge1Name);
+ static::assertEquals('edge1', $result3->getKey(), 'Did not return edge1!');
$edge1a->setFrom($result1->getInternalId());
$edge1a->setTo($result2->getInternalId());
@@ -920,7 +920,7 @@ public function testSaveVerticesAndConditionalSaveReplaceUpdateAndRemoveEdge()
$this->edge1Name,
$this->edgeLabel1,
$edge1a,
- ['revision' => $result1->getRevision()]
+ ['revision' => $result3->getRevision()]
);
static::assertTrue($result1a, 'Did not return true!');
From 40cfded2e5d87c4b4e59cbb52829c2f69d067fae Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Fri, 28 Jun 2019 17:05:35 +0200
Subject: [PATCH 009/101] added the ability to create TTL indexes via the
driver
---
lib/ArangoDBClient/CollectionHandler.php | 43 ++++++++++++---
tests/CollectionBasicTest.php | 69 ++++++++++++++----------
2 files changed, 77 insertions(+), 35 deletions(-)
diff --git a/lib/ArangoDBClient/CollectionHandler.php b/lib/ArangoDBClient/CollectionHandler.php
index aa82681e..ae02c692 100644
--- a/lib/ArangoDBClient/CollectionHandler.php
+++ b/lib/ArangoDBClient/CollectionHandler.php
@@ -161,6 +161,16 @@ class CollectionHandler extends Handler
* persistent index option
*/
const OPTION_PERSISTENT_INDEX = 'persistent';
+
+ /**
+ * ttl index option
+ */
+ const OPTION_TTL_INDEX = 'ttl';
+
+ /**
+ * expireAfter option
+ */
+ const OPTION_EXPIRE_AFTER = 'expireAfter';
/**
* sparse index option
@@ -848,7 +858,7 @@ public function import(
* @param boolean $unique - whether the values in the index should be unique or not
* @param boolean $sparse - whether the index should be sparse
*
- * @link https://docs.arangodb.com/HTTP/Indexes/Hash.html
+ * @link https://www.arangodb.com/docs/devel/indexing-hash.html
*
* @return array - server response of the created index
* @throws \ArangoDBClient\Exception
@@ -874,7 +884,7 @@ public function createHashIndex($collectionId, array $fields, $unique = null, $s
* @param array $fields - an array of fields
* @param int $minLength - the minimum length of words to index
*
- * @link https://docs.arangodb.com/HTTP/Indexes/Fulltext.html
+ * @link https://www.arangodb.com/docs/devel/indexing-fulltext.html
*
* @return array - server response of the created index
* @throws \ArangoDBClient\Exception
@@ -898,7 +908,7 @@ public function createFulltextIndex($collectionId, array $fields, $minLength = n
* @param bool $unique - whether the index is unique or not
* @param bool $sparse - whether the index should be sparse
*
- * @link https://docs.arangodb.com/HTTP/Indexes/Skiplist.html
+ * @link https://www.arangodb.com/docs/devel/indexing-skiplist.html
*
* @return array - server response of the created index
* @throws \ArangoDBClient\Exception
@@ -925,7 +935,7 @@ public function createSkipListIndex($collectionId, array $fields, $unique = null
* @param bool $unique - whether the index is unique or not
* @param bool $sparse - whether the index should be sparse
*
- * @link https://docs.arangodb.com/HTTP/Indexes/Persistent.html
+ * @link https://www.arangodb.com/docs/devel/indexing-persistent.html
*
* @return array - server response of the created index
* @throws \ArangoDBClient\Exception
@@ -943,6 +953,27 @@ public function createPersistentIndex($collectionId, array $fields, $unique = nu
return $this->index($collectionId, self::OPTION_PERSISTENT_INDEX, $fields, null, $indexOptions);
}
+
+ /**
+ * Create a TTL index
+ *
+ * @param string $collectionId - the collection id
+ * @param array $fields - an array of fields (only a single one allowed)
+ * @param number $expireAfter - number of seconds after index value after which documents expire
+ *
+ * @link https://www.arangodb.com/docs/devel/indexing-ttl.html
+ *
+ * @return array - server response of the created index
+ * @throws \ArangoDBClient\Exception
+ */
+ public function createTtlIndex($collectionId, array $fields, $expireAfter)
+ {
+ $indexOptions = [
+ self::OPTION_EXPIRE_AFTER => (double) $expireAfter
+ ];
+
+ return $this->index($collectionId, self::OPTION_TTL_INDEX, $fields, null, $indexOptions);
+ }
/**
* Create a geo index
@@ -951,7 +982,7 @@ public function createPersistentIndex($collectionId, array $fields, $unique = nu
* @param array $fields - an array of fields
* @param boolean $geoJson - whether to use geoJson or not
*
- * @link https://docs.arangodb.com/HTTP/Indexes/Geo.html
+ * @link https://www.arangodb.com/docs/devel/indexing-geo.html
*
* @return array - server response of the created index
* @throws \ArangoDBClient\Exception
@@ -977,7 +1008,7 @@ public function createGeoIndex($collectionId, array $fields, $geoJson = null)
* @throws Exception
*
* @param mixed $collectionId - The id of the collection where the index is to be created
- * @param string $type - index type: hash, skiplist, geo, fulltext, or persistent
+ * @param string $type - index type: hash, skiplist, geo, ttl, fulltext, or persistent
* @param array $attributes - an array of attributes that can be defined like array('a') or array('a', 'b.c')
* @param bool $unique - true/false to create a unique index
* @param array $indexOptions - an associative array of options for the index like array('geoJson' => true, 'sparse' => false)
diff --git a/tests/CollectionBasicTest.php b/tests/CollectionBasicTest.php
index 02c8abfc..6b7df06f 100644
--- a/tests/CollectionBasicTest.php
+++ b/tests/CollectionBasicTest.php
@@ -14,8 +14,6 @@
* @property Connection connection
* @property Collection collection
* @property CollectionHandler collectionHandler
- * @property bool hasSparseIndexes
- * @property bool hasSelectivityEstimates
*/
class CollectionBasicTest extends
\PHPUnit_Framework_TestCase
@@ -43,9 +41,6 @@ public function setUp()
$adminHandler = new AdminHandler($this->connection);
$version = preg_replace('/-[a-z0-9]+$/', '', $adminHandler->getServerVersion());
- $this->hasSparseIndexes = (version_compare($version, '2.5.0') >= 0);
- $this->hasSelectivityEstimates = (version_compare($version, '2.5.0') >= 0);
-
$this->isMMFilesEngine = ($adminHandler->getEngine()["name"] == "mmfiles");
}
@@ -730,12 +725,8 @@ public function testCreateHashIndex()
static::assertEquals('hashfield2', $indexInfo['fields'][1], "The second indexed field is not 'hashfield2'");
static::assertTrue($indexInfo[CollectionHandler::OPTION_UNIQUE], 'unique was not set to true!');
- if ($this->hasSparseIndexes) {
- static::assertFalse($indexInfo[CollectionHandler::OPTION_SPARSE], 'sparse flag was not set to false!');
- }
- if ($this->hasSelectivityEstimates) {
- static::assertTrue(isset($indexInfo['selectivityEstimate']), 'selectivity estimate not present!');
- }
+ static::assertFalse($indexInfo[CollectionHandler::OPTION_SPARSE], 'sparse flag was not set to false!');
+ static::assertTrue(isset($indexInfo['selectivityEstimate']), 'selectivity estimate not present!');
}
@@ -769,12 +760,8 @@ public function testCreateSparseHashIndex()
static::assertEquals('hashfield2', $indexInfo['fields'][1], "The second indexed field is not 'hashfield2'");
static::assertFalse($indexInfo[CollectionHandler::OPTION_UNIQUE], 'unique was not set to false!');
- if ($this->hasSparseIndexes) {
- static::assertTrue($indexInfo[CollectionHandler::OPTION_SPARSE], 'sparse flag was not set to true!');
- }
- if ($this->hasSelectivityEstimates) {
- static::assertTrue(isset($indexInfo['selectivityEstimate']), 'selectivity estimate not present!');
- }
+ static::assertTrue($indexInfo[CollectionHandler::OPTION_SPARSE], 'sparse flag was not set to true!');
+ static::assertTrue(isset($indexInfo['selectivityEstimate']), 'selectivity estimate not present!');
}
@@ -836,9 +823,7 @@ public function testCreateSkipListIndex()
static::assertEquals('skiplistfield1', $indexInfo['fields'][0], "The indexed field is not 'skiplistfield1'");
static::assertEquals('skiplistfield2', $indexInfo['fields'][1], "The indexed field is not 'skiplistfield2'");
static::assertTrue($indexInfo[CollectionHandler::OPTION_UNIQUE], 'unique was not set to true!');
- if ($this->hasSparseIndexes) {
- static::assertFalse($indexInfo[CollectionHandler::OPTION_SPARSE], 'sparse flag was not set to false!');
- }
+ static::assertFalse($indexInfo[CollectionHandler::OPTION_SPARSE], 'sparse flag was not set to false!');
}
@@ -871,9 +856,7 @@ public function testCreateSparseSkipListIndex()
static::assertEquals('skiplistfield1', $indexInfo['fields'][0], "The indexed field is not 'skiplistfield1'");
static::assertEquals('skiplistfield2', $indexInfo['fields'][1], "The indexed field is not 'skiplistfield2'");
static::assertFalse($indexInfo[CollectionHandler::OPTION_UNIQUE], 'unique was not set to false!');
- if ($this->hasSparseIndexes) {
- static::assertTrue($indexInfo[CollectionHandler::OPTION_SPARSE], 'sparse flag was not set to true!');
- }
+ static::assertTrue($indexInfo[CollectionHandler::OPTION_SPARSE], 'sparse flag was not set to true!');
}
@@ -905,9 +888,7 @@ public function testCreatePersistentIndex()
static::assertEquals('field1', $indexInfo['fields'][0], "The indexed field is not 'field1'");
static::assertEquals('field2', $indexInfo['fields'][1], "The indexed field is not 'field2'");
static::assertTrue($indexInfo[CollectionHandler::OPTION_UNIQUE], 'unique was not set to true!');
- if ($this->hasSparseIndexes) {
- static::assertFalse($indexInfo[CollectionHandler::OPTION_SPARSE], 'sparse flag was not set to false!');
- }
+ static::assertFalse($indexInfo[CollectionHandler::OPTION_SPARSE], 'sparse flag was not set to false!');
}
@@ -940,9 +921,39 @@ public function testCreateSparsePersistentIndex()
static::assertEquals('field1', $indexInfo['fields'][0], "The indexed field is not 'field1'");
static::assertEquals('field2', $indexInfo['fields'][1], "The indexed field is not 'field2'");
static::assertFalse($indexInfo[CollectionHandler::OPTION_UNIQUE], 'unique was not set to false!');
- if ($this->hasSparseIndexes) {
- static::assertTrue($indexInfo[CollectionHandler::OPTION_SPARSE], 'sparse flag was not set to true!');
- }
+ static::assertTrue($indexInfo[CollectionHandler::OPTION_SPARSE], 'sparse flag was not set to true!');
+ }
+
+
+ /**
+ * Create a TTL index and verify it by getting information about the index from the server
+ */
+ public function testCreateTtlIndex()
+ {
+ $result = $this->collectionHandler->createTtlIndex(
+ 'ArangoDB_PHP_TestSuite_IndexTestCollection' . '_' . static::$testsTimestamp,
+ ['expireStamp'],
+ 60
+ );
+
+ $indices = $this->collectionHandler->getIndexes('ArangoDB_PHP_TestSuite_IndexTestCollection' . '_' . static::$testsTimestamp);
+
+ $indicesByIdentifiers = $indices['identifiers'];
+
+ static::assertArrayHasKey($result['id'], $indicesByIdentifiers, 'TTL index was not created!');
+
+ $indexInfo = $indicesByIdentifiers[$result['id']];
+
+ static::assertEquals(
+ CollectionHandler::OPTION_TTL_INDEX,
+ $indexInfo[CollectionHandler::OPTION_TYPE],
+ "Index type is not 'ttl'!"
+ );
+ static::assertCount(1, $indexInfo['fields'], 'There should only be 1 indexed field');
+ static::assertEquals('expireStamp', $indexInfo['fields'][0], "The indexed field is not 'expireStamp'");
+ static::assertEquals(60, $indexInfo[CollectionHandler::OPTION_EXPIRE_AFTER]);
+ static::assertFalse($indexInfo[CollectionHandler::OPTION_UNIQUE], 'unique was not set to false!');
+ static::assertTrue($indexInfo[CollectionHandler::OPTION_SPARSE], 'sparse flag was not set to false!');
}
From cc17088ffd91efee83a585f66750399af3efdda1 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Fri, 28 Jun 2019 17:08:36 +0200
Subject: [PATCH 010/101] use RC4
---
tests/travis/setup_arangodb.sh | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tests/travis/setup_arangodb.sh b/tests/travis/setup_arangodb.sh
index ebd460c4..ddab1448 100644
--- a/tests/travis/setup_arangodb.sh
+++ b/tests/travis/setup_arangodb.sh
@@ -35,8 +35,8 @@ echo "./phpunit --version"
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $DIR
-docker pull arangodb/arangodb-preview:devel
-docker run -d -e ARANGO_ROOT_PASSWORD="test" -p 8529:8529 arangodb/arangodb-preview:devel
+docker pull arangodb/arangodb-preview:3.5.0-rc.4
+docker run -d -e ARANGO_ROOT_PASSWORD="test" -p 8529:8529 arangodb/arangodb-preview:3.5.0-rc.4
sleep 2
From b153bcf133c76210b2df1cceaf57ad00d50cbdb7 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Mon, 1 Jul 2019 12:58:40 +0200
Subject: [PATCH 011/101] added support for background indexes, ttl indexes and
some more collection attributes
---
CHANGELOG.md | 15 +++
lib/ArangoDBClient/Collection.php | 129 ++++++++++++++----
lib/ArangoDBClient/CollectionHandler.php | 77 ++++++++---
tests/CollectionBasicTest.php | 159 +++++++++++++++++++++++
tests/bootstrap.php | 15 +++
5 files changed, 349 insertions(+), 46 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7e75ddc2..e7d73294 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,18 @@
+Release notes for the ArangoDB-PHP driver 3.5.x
+===============================================
+
+The `CollectionHandler` class got a new method `createTtlIndex` for creating time-to-live (TTL)
+indexes on the server.
+
+All methods for index creation also got an extra optional attribute `$inBackground` that enables
+background index creation.
+
+Added support for the following attributes on collection level:
+
+- distributeShardsLike
+- smartJoinAttribute (only effective in ArangoDB enterprise edition)
+
+
Release notes for the ArangoDB-PHP driver 3.4.x
===============================================
diff --git a/lib/ArangoDBClient/Collection.php b/lib/ArangoDBClient/Collection.php
index fc6f4d82..75d7f5b2 100644
--- a/lib/ArangoDBClient/Collection.php
+++ b/lib/ArangoDBClient/Collection.php
@@ -68,6 +68,13 @@ class Collection
* @var bool - isVolatile value
*/
private $_isVolatile;
+
+ /**
+ * The distributeShardsLike value (might be NULL for new collections)
+ *
+ * @var mixed - distributeShardsLike value
+ */
+ private $_distributeShardsLike;
/**
* The collection numberOfShards value (might be NULL for new collections)
@@ -96,6 +103,13 @@ class Collection
* @var array - shardKeys value
*/
private $_shardKeys;
+
+ /**
+ * The smartJoinAttribute value (might be NULL for new collections)
+ *
+ * @var mixed - smartJoinAttribute value
+ */
+ private $_smartJoinAttribute;
/**
* The collection status value
@@ -155,6 +169,11 @@ class Collection
* Collection 'isVolatile' index
*/
const ENTRY_IS_VOLATILE = 'isVolatile';
+
+ /**
+ * Collection 'distributeShardsLike' index
+ */
+ const ENTRY_DISTRIBUTE_SHARDS_LIKE = 'distributeShardsLike';
/**
* Collection 'numberOfShards' index
@@ -170,11 +189,16 @@ class Collection
* Collection 'shardingStrategy' index
*/
const ENTRY_SHARDING_STRATEGY = 'shardingStrategy';
-
+
/**
* Collection 'shardKeys' index
*/
const ENTRY_SHARD_KEYS = 'shardKeys';
+
+ /**
+ * Collection 'smartJoinAttribute' index
+ */
+ const ENTRY_SMART_JOIN_ATTRIBUTE = 'smartJoinAttribute';
/**
* properties option
@@ -271,16 +295,18 @@ public static function getDefaultType()
*/
public function __clone()
{
- $this->_id = null;
- $this->_name = null;
- $this->_waitForSync = null;
- $this->_journalSize = null;
- $this->_isSystem = null;
- $this->_isVolatile = null;
- $this->_numberOfShards = null;
- $this->_replicationFactor = null;
- $this->_shardingStrategy = null;
- $this->_shardKeys = null;
+ $this->_id = null;
+ $this->_name = null;
+ $this->_waitForSync = null;
+ $this->_journalSize = null;
+ $this->_isSystem = null;
+ $this->_isVolatile = null;
+ $this->_distributeShardsLike = null;
+ $this->_numberOfShards = null;
+ $this->_replicationFactor = null;
+ $this->_shardingStrategy = null;
+ $this->_shardKeys = null;
+ $this->_smartJoinAttribute = null;
}
/**
@@ -335,6 +361,10 @@ public function getAll()
self::ENTRY_STATUS => $this->_status,
self::ENTRY_KEY_OPTIONS => $this->_keyOptions
];
+
+ if (null !== $this->_distributeShardsLike) {
+ $result[self::ENTRY_DISTRIBUTE_SHARDS_LIKE] = $this->_distributeShardsLike;
+ }
if (null !== $this->_numberOfShards) {
$result[self::ENTRY_NUMBER_OF_SHARDS] = $this->_numberOfShards;
@@ -347,10 +377,14 @@ public function getAll()
if (null !== $this->_shardingStrategy) {
$result[self::ENTRY_SHARDING_STRATEGY] = $this->_shardingStrategy;
}
-
+
if (is_array($this->_shardKeys)) {
$result[self::ENTRY_SHARD_KEYS] = $this->_shardKeys;
}
+
+ if (null !== $this->_smartJoinAttribute) {
+ $result[self::ENTRY_SMART_JOIN_ATTRIBUTE] = $this->_smartJoinAttribute;
+ }
return $result;
}
@@ -378,79 +412,76 @@ public function set($key, $value)
if ($key === self::ENTRY_ID) {
$this->setId($value);
-
return;
}
if ($key === self::ENTRY_NAME) {
$this->setName($value);
-
return;
}
if ($key === self::ENTRY_WAIT_SYNC) {
$this->setWaitForSync($value);
-
return;
}
if ($key === self::ENTRY_JOURNAL_SIZE) {
$this->setJournalSize($value);
-
return;
}
if ($key === self::ENTRY_IS_SYSTEM) {
$this->setIsSystem($value);
-
return;
}
if ($key === self::ENTRY_IS_VOLATILE) {
$this->setIsVolatile($value);
-
return;
}
if ($key === self::ENTRY_TYPE) {
$this->setType($value);
-
return;
}
if ($key === self::ENTRY_STATUS) {
$this->setStatus($value);
-
return;
}
if ($key === self::ENTRY_KEY_OPTIONS) {
$this->setKeyOptions($value);
-
+ return;
+ }
+
+ if ($key === self::ENTRY_DISTRIBUTE_SHARDS_LIKE) {
+ $this->setDistributeShardsLike($value);
return;
}
if ($key === self::ENTRY_NUMBER_OF_SHARDS) {
$this->setNumberOfShards($value);
-
return;
}
if ($key === self::ENTRY_REPLICATION_FACTOR) {
$this->setReplicationFactor($value);
-
return;
}
if ($key === self::ENTRY_SHARDING_STRATEGY) {
$this->setShardingStrategy($value);
-
return;
}
-
+
if ($key === self::ENTRY_SHARD_KEYS) {
$this->setShardKeys($value);
-
+ return;
+ }
+
+ if ($key === self::ENTRY_SMART_JOIN_ATTRIBUTE) {
+ $this->setSmartJoinAttribute($value);
return;
}
// unknown attribute, will be ignored
@@ -726,6 +757,28 @@ public function getIsVolatile()
{
return $this->_isVolatile;
}
+
+ /**
+ * Set the distribute shards like value
+ *
+ * @param string $value - distributeShardsLike value
+ *
+ * @return void
+ */
+ public function setDistributeShardsLike($value)
+ {
+ $this->_distributeShardsLike = $value;
+ }
+
+ /**
+ * Get the distributeShardsLike (if already known)
+ *
+ * @return mixed - distributeShardsLike value
+ */
+ public function getDistributeShardsLike()
+ {
+ return $this->_distributeShardsLike;
+ }
/**
* Set the numberOfShards value
@@ -795,7 +848,7 @@ public function getShardingStrategy()
{
return $this->_shardingStrategy;
}
-
+
/**
* Set the shardKeys value
*
@@ -818,6 +871,28 @@ public function getShardKeys()
{
return $this->_shardKeys;
}
+
+ /**
+ * Set the smart join attribute value
+ *
+ * @param string $value - smartJoinAttribute value
+ *
+ * @return void
+ */
+ public function setSmartJoinAttribute($value)
+ {
+ $this->_smartJoinAttribute = $value;
+ }
+
+ /**
+ * Get the smart join attribute value (if already known)
+ *
+ * @return mixed - smart join attribute value
+ */
+ public function getSmartJoinAttribute()
+ {
+ return $this->_smartJoinAttribute;
+ }
}
class_alias(Collection::class, '\triagens\ArangoDb\Collection');
diff --git a/lib/ArangoDBClient/CollectionHandler.php b/lib/ArangoDBClient/CollectionHandler.php
index ae02c692..adab99ca 100644
--- a/lib/ArangoDBClient/CollectionHandler.php
+++ b/lib/ArangoDBClient/CollectionHandler.php
@@ -171,6 +171,11 @@ class CollectionHandler extends Handler
* expireAfter option
*/
const OPTION_EXPIRE_AFTER = 'expireAfter';
+
+ /**
+ * inBackground option
+ */
+ const OPTION_IN_BACKGROUND = 'inBackground';
/**
* sparse index option
@@ -224,16 +229,18 @@ class CollectionHandler extends Handler
* @param mixed $collection - collection object to be created on the server or a string with the name
* @param array $options - an array of options.
*
Options are :
- *
'type' - 2 -> normal collection, 3 -> edge-collection
- *
'waitForSync' - if set to true, then all removal operations will instantly be synchronised to disk / If this is not specified, then the collection's default sync behavior will be applied.
'numberOfShards' - number of shards for the collection.
- *
'shardKeys' - array of shard key attributes.
- *
'replicationFactor' - number of replicas to keep (default: 1).
- *
'shardingStrategy' - sharding strategy to use in cluster.
+ *
'type' - 2 -> normal collection, 3 -> edge-collection
+ *
'waitForSync' - if set to true, then all removal operations will instantly be synchronised to disk / If this is not specified, then the collection's default sync behavior will be applied.
'distributeShardsLike' - name of prototype collection for identical sharding.
+ *
'numberOfShards' - number of shards for the collection.
+ *
'replicationFactor' - number of replicas to keep (default: 1).
+ *
'shardKeys' - array of shard key attributes.
+ *
'shardingStrategy' - sharding strategy to use in cluster.
+ *
'smartJoinAttribute' - attribute name for smart joins (if not shard key).
*
*
* @return mixed - id of collection created
@@ -276,6 +283,10 @@ public function create($collection, array $options = [])
];
// set extra cluster attributes
+ if ($collection->getDistributeShardsLike() !== null) {
+ $params[Collection::ENTRY_DISTRIBUTE_SHARDS_LIKE] = $collection->getDistributeShardsLike();
+ }
+
if ($collection->getNumberOfShards() !== null) {
$params[Collection::ENTRY_NUMBER_OF_SHARDS] = $collection->getNumberOfShards();
}
@@ -291,6 +302,10 @@ public function create($collection, array $options = [])
if (is_array($collection->getShardKeys())) {
$params[Collection::ENTRY_SHARD_KEYS] = $collection->getShardKeys();
}
+
+ if ($collection->getSmartJoinAttribute() !== null) {
+ $params[Collection::ENTRY_SMART_JOIN_ATTRIBUTE] = $collection->getSmartJoinAttribute();
+ }
$response = $this->getConnection()->post(Urls::URL_COLLECTION, $this->json_encode_wrapper($params));
@@ -855,15 +870,16 @@ public function import(
*
* @param string $collectionId - the collection id
* @param array $fields - an array of fields
- * @param boolean $unique - whether the values in the index should be unique or not
- * @param boolean $sparse - whether the index should be sparse
+ * @param bool $unique - whether the values in the index should be unique or not
+ * @param bool $sparse - whether the index should be sparse
+ * @param bool $inBackground - true if index shall be created in background
*
* @link https://www.arangodb.com/docs/devel/indexing-hash.html
*
* @return array - server response of the created index
* @throws \ArangoDBClient\Exception
*/
- public function createHashIndex($collectionId, array $fields, $unique = null, $sparse = null)
+ public function createHashIndex($collectionId, array $fields, $unique = null, $sparse = null, $inBackground = false)
{
$indexOptions = [];
@@ -873,6 +889,9 @@ public function createHashIndex($collectionId, array $fields, $unique = null, $s
if ($sparse) {
$indexOptions[self::OPTION_SPARSE] = (bool) $sparse;
}
+ if ($inBackground) {
+ $indexOptions[self::OPTION_IN_BACKGROUND] = (bool) $inBackground;
+ }
return $this->index($collectionId, self::OPTION_HASH_INDEX, $fields, null, $indexOptions);
}
@@ -883,19 +902,23 @@ public function createHashIndex($collectionId, array $fields, $unique = null, $s
* @param string $collectionId - the collection id
* @param array $fields - an array of fields
* @param int $minLength - the minimum length of words to index
+ * @param bool $inBackground - true if index shall be created in background
*
* @link https://www.arangodb.com/docs/devel/indexing-fulltext.html
*
* @return array - server response of the created index
* @throws \ArangoDBClient\Exception
*/
- public function createFulltextIndex($collectionId, array $fields, $minLength = null)
+ public function createFulltextIndex($collectionId, array $fields, $minLength = null, $inBackground = false)
{
$indexOptions = [];
if ($minLength) {
$indexOptions[self::OPTION_MIN_LENGTH] = $minLength;
}
+ if ($inBackground) {
+ $indexOptions[self::OPTION_IN_BACKGROUND] = (bool) $inBackground;
+ }
return $this->index($collectionId, self::OPTION_FULLTEXT_INDEX, $fields, null, $indexOptions);
}
@@ -907,13 +930,14 @@ public function createFulltextIndex($collectionId, array $fields, $minLength = n
* @param array $fields - an array of fields
* @param bool $unique - whether the index is unique or not
* @param bool $sparse - whether the index should be sparse
+ * @param bool $inBackground - true if index shall be created in background
*
* @link https://www.arangodb.com/docs/devel/indexing-skiplist.html
*
* @return array - server response of the created index
* @throws \ArangoDBClient\Exception
*/
- public function createSkipListIndex($collectionId, array $fields, $unique = null, $sparse = null)
+ public function createSkipListIndex($collectionId, array $fields, $unique = null, $sparse = null, $inBackground = false)
{
$indexOptions = [];
@@ -923,6 +947,9 @@ public function createSkipListIndex($collectionId, array $fields, $unique = null
if ($sparse) {
$indexOptions[self::OPTION_SPARSE] = (bool) $sparse;
}
+ if ($inBackground) {
+ $indexOptions[self::OPTION_IN_BACKGROUND] = (bool) $inBackground;
+ }
return $this->index($collectionId, self::OPTION_SKIPLIST_INDEX, $fields, null, $indexOptions);
}
@@ -934,13 +961,14 @@ public function createSkipListIndex($collectionId, array $fields, $unique = null
* @param array $fields - an array of fields
* @param bool $unique - whether the index is unique or not
* @param bool $sparse - whether the index should be sparse
+ * @param bool $inBackground - true if index shall be created in background
*
* @link https://www.arangodb.com/docs/devel/indexing-persistent.html
*
* @return array - server response of the created index
* @throws \ArangoDBClient\Exception
*/
- public function createPersistentIndex($collectionId, array $fields, $unique = null, $sparse = null)
+ public function createPersistentIndex($collectionId, array $fields, $unique = null, $sparse = null, $inBackground = false)
{
$indexOptions = [];
@@ -950,6 +978,9 @@ public function createPersistentIndex($collectionId, array $fields, $unique = nu
if ($sparse) {
$indexOptions[self::OPTION_SPARSE] = (bool) $sparse;
}
+ if ($inBackground) {
+ $indexOptions[self::OPTION_IN_BACKGROUND] = (bool) $inBackground;
+ }
return $this->index($collectionId, self::OPTION_PERSISTENT_INDEX, $fields, null, $indexOptions);
}
@@ -960,17 +991,21 @@ public function createPersistentIndex($collectionId, array $fields, $unique = nu
* @param string $collectionId - the collection id
* @param array $fields - an array of fields (only a single one allowed)
* @param number $expireAfter - number of seconds after index value after which documents expire
+ * @param bool $inBackground - true if index shall be created in background
*
* @link https://www.arangodb.com/docs/devel/indexing-ttl.html
*
* @return array - server response of the created index
* @throws \ArangoDBClient\Exception
*/
- public function createTtlIndex($collectionId, array $fields, $expireAfter)
+ public function createTtlIndex($collectionId, array $fields, $expireAfter, $inBackground = false)
{
$indexOptions = [
self::OPTION_EXPIRE_AFTER => (double) $expireAfter
];
+ if ($inBackground) {
+ $indexOptions[self::OPTION_IN_BACKGROUND] = (bool) $inBackground;
+ }
return $this->index($collectionId, self::OPTION_TTL_INDEX, $fields, null, $indexOptions);
}
@@ -980,20 +1015,24 @@ public function createTtlIndex($collectionId, array $fields, $expireAfter)
*
* @param string $collectionId - the collection id
* @param array $fields - an array of fields
- * @param boolean $geoJson - whether to use geoJson or not
+ * @param bool $geoJson - whether to use geoJson or not
+ * @param bool $inBackground - true if index shall be created in background
*
* @link https://www.arangodb.com/docs/devel/indexing-geo.html
*
* @return array - server response of the created index
* @throws \ArangoDBClient\Exception
*/
- public function createGeoIndex($collectionId, array $fields, $geoJson = null)
+ public function createGeoIndex($collectionId, array $fields, $geoJson = null, $inBackground = false)
{
$indexOptions = [];
if ($geoJson) {
$indexOptions[self::OPTION_GEOJSON] = (bool) $geoJson;
}
+ if ($inBackground) {
+ $indexOptions[self::OPTION_IN_BACKGROUND] = (bool) $inBackground;
+ }
return $this->index($collectionId, self::OPTION_GEO_INDEX, $fields, null, $indexOptions);
}
diff --git a/tests/CollectionBasicTest.php b/tests/CollectionBasicTest.php
index 6b7df06f..6ffd8453 100644
--- a/tests/CollectionBasicTest.php
+++ b/tests/CollectionBasicTest.php
@@ -217,6 +217,42 @@ public function testCreateCollectionWithKeyOptionsCluster()
static::assertEquals(501, $e->getCode());
}
+
+
+ /**
+ * Try to create a collection with distributeShardsLike
+ */
+ public function testCreateCollectionWithDistributeShardsLike()
+ {
+ if (!isCluster($this->connection)) {
+ // don't execute this test in a non-cluster
+ $this->markTestSkipped("test is only meaningful in cluster");
+ return;
+ }
+
+ $connection = $this->connection;
+ $collectionHandler = new CollectionHandler($connection);
+
+ $collection1 = new Collection();
+ $name1 = 'ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp;
+ $collection1->setName($name1);
+ $response = $collectionHandler->create($collection1);
+
+ $resultingCollection = $collectionHandler->getProperties($response);
+ $properties = $resultingCollection->getAll();
+ static::assertFalse(array_key_exists(Collection::ENTRY_DISTRIBUTE_SHARDS_LIKE, $properties));
+
+ $collection2 = new Collection();
+ $name2 = 'ArangoDB_PHP_TestSuite_TestCollection_02' . '_' . static::$testsTimestamp;
+ $collection2->setName($name2);
+ $collection2->setDistributeShardsLike($name1);
+ $response = $collectionHandler->create($collection2);
+
+ $resultingCollection = $collectionHandler->getProperties($response);
+ $properties = $resultingCollection->getAll();
+
+ static::assertEquals($name1, $properties[Collection::ENTRY_DISTRIBUTE_SHARDS_LIKE]);
+ }
/**
@@ -323,6 +359,46 @@ public function testCreateCollectionWithReplicationFactor2()
static::assertEquals(2, $properties[Collection::ENTRY_REPLICATION_FACTOR]);
}
+ /**
+ * Try to create a collection with replication factor "satellite"
+ */
+ public function testCreateCollectionWithReplicationFactorSatellite()
+ {
+ if (!isCluster($this->connection)) {
+ // don't execute this test in a non-cluster
+ $this->markTestSkipped("test is only meaningful in cluster");
+ return;
+ }
+
+ if (!isEnterprise($this->connection)) {
+ // don't execute this test in community version
+ $this->markTestSkipped("test is only meaningful in enterprise version");
+ return;
+ }
+
+ $connection = $this->connection;
+ $collection = new Collection();
+ $collectionHandler = new CollectionHandler($connection);
+
+ $name = 'ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp;
+
+ try {
+ $collectionHandler->drop($name);
+ } catch (Exception $e) {
+ //Silence the exception
+ }
+
+ $collection->setName($name);
+ $collection->setReplicationFactor("satellite");
+
+ $response = $collectionHandler->create($collection);
+
+ $resultingCollection = $collectionHandler->getProperties($response);
+ $properties = $resultingCollection->getAll();
+
+ static::assertEquals("satellite", $properties[Collection::ENTRY_REPLICATION_FACTOR]);
+ }
+
/**
* Try to create a collection with an explicit sharding strategy
@@ -470,6 +546,48 @@ public function testCreateCollectionWithShardKeysCluster()
'Shard keys do not match.'
);
}
+
+ /**
+ * Try to create a collection with smart join attribute
+ */
+ public function testCreateCollectionWithSmartJoinAttribute()
+ {
+ if (!isCluster($this->connection)) {
+ // don't execute this test in a non-cluster
+ $this->markTestSkipped("test is only meaningful in cluster");
+ return;
+ }
+
+ if (!isEnterprise($this->connection)) {
+ // don't execute this test in community version
+ $this->markTestSkipped("test is only meaningful in enterprise version");
+ return;
+ }
+
+ $connection = $this->connection;
+ $collection = new Collection();
+ $collectionHandler = new CollectionHandler($connection);
+
+ $name = 'ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp;
+
+ try {
+ $collectionHandler->drop($name);
+ } catch (Exception $e) {
+ //Silence the exception
+ }
+
+ $collection->setName($name);
+ $collection->setShardKeys(['_key:']);
+ $collection->setSmartJoinAttribute("myAttribute");
+
+ $response = $collectionHandler->create($collection);
+
+ $resultingCollection = $collectionHandler->getProperties($response);
+ $properties = $resultingCollection->getAll();
+
+ static::assertEquals([ '_key:' ], $properties[Collection::ENTRY_SHARD_KEYS]);
+ static::assertEquals("myAttribute", $properties[Collection::ENTRY_SMART_JOIN_ATTRIBUTE]);
+ }
/**
@@ -982,6 +1100,37 @@ public function testGetIndex()
static::assertEquals('testGetIndexField', $indexInfo['fields'][0], 'Index field does not match!');
static::assertEquals(100, $indexInfo[CollectionHandler::OPTION_MIN_LENGTH], 'Min length does not match!');
}
+
+
+ /**
+ * Create an index in background and verify it by getting information about the index from the server
+ */
+ public function testCreateIndexInBackground()
+ {
+ $result = $this->collectionHandler->createHashIndex(
+ 'ArangoDB_PHP_TestSuite_IndexTestCollection' . '_' . static::$testsTimestamp,
+ ['test'],
+ false,
+ false,
+ true
+ );
+
+ $indices = $this->collectionHandler->getIndexes('ArangoDB_PHP_TestSuite_IndexTestCollection' . '_' . static::$testsTimestamp);
+
+ $indicesByIdentifiers = $indices['identifiers'];
+
+ static::assertArrayHasKey($result['id'], $indicesByIdentifiers);
+
+ $indexInfo = $indicesByIdentifiers[$result['id']];
+
+ static::assertEquals(
+ CollectionHandler::OPTION_HASH_INDEX,
+ $indexInfo[CollectionHandler::OPTION_TYPE]
+ );
+ static::assertEquals(['test'], $indexInfo['fields']);
+ static::assertFalse($indexInfo[CollectionHandler::OPTION_UNIQUE], 'unique was not set to false!');
+ static::assertFalse($indexInfo[CollectionHandler::OPTION_SPARSE], 'sparse flag was not set to false!');
+ }
public function testHasCollectionReturnsFalseIfCollectionDoesNotExist()
{
@@ -1000,6 +1149,16 @@ public function tearDown()
} catch (Exception $e) {
//Silence the exception
}
+ try {
+ $this->collectionHandler->drop('ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp);
+ } catch (Exception $e) {
+ //Silence the exception
+ }
+ try {
+ $this->collectionHandler->drop('ArangoDB_PHP_TestSuite_TestCollection_02' . '_' . static::$testsTimestamp);
+ } catch (Exception $e) {
+ //Silence the exception
+ }
unset($this->collectionHandler, $this->connection);
}
}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 165fd0b0..63f84a99 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -9,6 +9,21 @@
namespace ArangoDBClient;
+function isEnterprise(Connection $connection)
+{
+ static $isEnterprise = null;
+
+ if ($isEnterprise === null) {
+ $adminHandler = new AdminHandler($connection);
+ $version = $adminHandler->getServerVersion(true);
+ if (@$version["details"]["enterprise-version"] === "enterprise") {
+ $isEnterprise = true;
+ }
+ }
+
+ return $isEnterprise;
+}
+
function isCluster(Connection $connection)
{
static $isCluster = null;
From 267e90ff9d19ba01ec8802ac9cf91c8fd2352d54 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Fri, 2 Aug 2019 15:33:27 +0200
Subject: [PATCH 012/101] store redundant regex part in a constant
---
lib/ArangoDBClient/Document.php | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/lib/ArangoDBClient/Document.php b/lib/ArangoDBClient/Document.php
index 0e7384fd..f2fc58c1 100644
--- a/lib/ArangoDBClient/Document.php
+++ b/lib/ArangoDBClient/Document.php
@@ -129,6 +129,11 @@ class Document implements \JsonSerializable
* keepNull option index
*/
const OPTION_KEEPNULL = 'keepNull';
+
+ /**
+ * regular expression used for key validation
+ */
+ const KEY_REGEX_PART = '[a-zA-Z0-9_:.@\\-()+,=;$!*\'%]{1,254}';
/**
* Constructs an empty document
@@ -299,25 +304,21 @@ public function set($key, $value)
if ($key[0] === '_') {
if ($key === self::ENTRY_ID) {
$this->setInternalId($value);
-
return;
}
if ($key === self::ENTRY_KEY) {
$this->setInternalKey($value);
-
return;
}
if ($key === self::ENTRY_REV) {
$this->setRevision($value);
-
return;
}
if ($key === self::ENTRY_ISNEW) {
$this->setIsNew($value);
-
return;
}
}
@@ -638,7 +639,7 @@ public function setInternalId($id)
}
- if (!preg_match('/^[a-zA-Z0-9_-]{1,64}\/[a-zA-Z0-9_:.@\-()+,=;$!*\'%]{1,254}$/', $id)) {
+ if (!preg_match('/^[a-zA-Z0-9_-]{1,64}\/' . self::KEY_REGEX_PART . '$/', $id)) {
throw new ClientException('Invalid format for document id');
}
@@ -662,7 +663,7 @@ public function setInternalKey($key)
throw new ClientException('Should not update the key of an existing document');
}
- if (!preg_match('/^[a-zA-Z0-9_:.@\-()+,=;$!*\'%]{1,254}$/', $key)) {
+ if (!preg_match('/^' . self::KEY_REGEX_PART . '$/', $key)) {
throw new ClientException('Invalid format for document key');
}
From a384df66b29aea9d976555c5d756cb3a2217266e Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Fri, 2 Aug 2019 15:47:24 +0200
Subject: [PATCH 013/101] remove unused attribute and method from
AqlUserFunction
---
CHANGELOG.md | 3 +++
lib/ArangoDBClient/AqlUserFunction.php | 22 +---------------------
2 files changed, 4 insertions(+), 21 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e7d73294..fb0e8e2e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,9 @@ Added support for the following attributes on collection level:
- distributeShardsLike
- smartJoinAttribute (only effective in ArangoDB enterprise edition)
+Removed unused `$_action` member in class `AqlUserFunction`, also
+removed its `__toString()` method.
+
Release notes for the ArangoDB-PHP driver 3.4.x
===============================================
diff --git a/lib/ArangoDBClient/AqlUserFunction.php b/lib/ArangoDBClient/AqlUserFunction.php
index a994b744..7bc252ff 100644
--- a/lib/ArangoDBClient/AqlUserFunction.php
+++ b/lib/ArangoDBClient/AqlUserFunction.php
@@ -43,7 +43,6 @@
*
* @property string $name - The name of the user function
* @property string $code - The code of the user function
- * @property string _action
*
* @package ArangoDBClient
* @since 1.3
@@ -58,19 +57,12 @@ class AqlUserFunction
private $_connection;
/**
- * The transaction's attributes.
+ * The function's attributes.
*
* @var array
*/
protected $attributes = [];
- /**
- * The transaction's action.
- *
- * @var string
- */
- protected $_action = '';
-
/**
* Collections index
*/
@@ -367,18 +359,6 @@ public function __get($key)
}
- /**
- * Returns the action string
- *
- * @magic
- *
- * @return string - the current action string
- */
- public function __toString()
- {
- return $this->_action;
- }
-
/**
* Build the object's attributes from a given array
*
From 8ff842c6e49b56e3f0c24a4830e0abe8ee7345ac Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Fri, 2 Aug 2019 16:00:29 +0200
Subject: [PATCH 014/101] added support for minReplicationFactor
---
CHANGELOG.md | 1 +
lib/ArangoDBClient/Collection.php | 49 +++++++++++++++++++++++-
lib/ArangoDBClient/CollectionHandler.php | 5 +++
tests/CollectionBasicTest.php | 5 +++
4 files changed, 58 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fb0e8e2e..7c0ca608 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ Added support for the following attributes on collection level:
- distributeShardsLike
- smartJoinAttribute (only effective in ArangoDB enterprise edition)
+- minReplicationFactor
Removed unused `$_action` member in class `AqlUserFunction`, also
removed its `__toString()` method.
diff --git a/lib/ArangoDBClient/Collection.php b/lib/ArangoDBClient/Collection.php
index 75d7f5b2..9c34dcea 100644
--- a/lib/ArangoDBClient/Collection.php
+++ b/lib/ArangoDBClient/Collection.php
@@ -90,6 +90,13 @@ class Collection
*/
private $_replicationFactor;
+ /**
+ * The minimum replicationFactor value for writes to be successful
+ *
+ * @var mixed - minimum replicationFactor value
+ */
+ private $_minReplicationFactor;
+
/**
* The shardingStrategy value (might be NULL for new collections)
*
@@ -185,6 +192,11 @@ class Collection
*/
const ENTRY_REPLICATION_FACTOR = 'replicationFactor';
+ /**
+ * Collection 'minReplicationFactor' index
+ */
+ const ENTRY_MIN_REPLICATION_FACTOR = 'minReplicationFactor';
+
/**
* Collection 'shardingStrategy' index
*/
@@ -304,6 +316,7 @@ public function __clone()
$this->_distributeShardsLike = null;
$this->_numberOfShards = null;
$this->_replicationFactor = null;
+ $this->_minReplicationFactor = null;
$this->_shardingStrategy = null;
$this->_shardKeys = null;
$this->_smartJoinAttribute = null;
@@ -374,6 +387,10 @@ public function getAll()
$result[self::ENTRY_REPLICATION_FACTOR] = $this->_replicationFactor;
}
+ if (null !== $this->_minReplicationFactor) {
+ $result[self::ENTRY_MIN_REPLICATION_FACTOR] = $this->_minReplicationFactor;
+ }
+
if (null !== $this->_shardingStrategy) {
$result[self::ENTRY_SHARDING_STRATEGY] = $this->_shardingStrategy;
}
@@ -470,6 +487,11 @@ public function set($key, $value)
return;
}
+ if ($key === self::ENTRY_MIN_REPLICATION_FACTOR) {
+ $this->setMinReplicationFactor($value);
+ return;
+ }
+
if ($key === self::ENTRY_SHARDING_STRATEGY) {
$this->setShardingStrategy($value);
return;
@@ -807,7 +829,7 @@ public function getNumberOfShards()
/**
* Set the replicationFactor value
*
- * @param int $value - replicationFactor value
+ * @param mixed $value - replicationFactor value (either a number, or "satellite")
*
* @return void
*/
@@ -828,7 +850,30 @@ public function getReplicationFactor()
}
/**
- * Set the shardingStragy value
+ * Set the minReplicationFactor value
+ *
+ * @param int $value - minReplicationFactor value
+ *
+ * @return void
+ */
+ public function setMinReplicationFactor($value)
+ {
+ assert(null === $value || is_numeric($value));
+ $this->_minReplicationFactor = $value;
+ }
+
+ /**
+ * Get the minReplicationFactor value (if already known)
+ *
+ * @return mixed - minReplicationFactor value
+ */
+ public function getMinReplicationFactor()
+ {
+ return $this->_minReplicationFactor;
+ }
+
+ /**
+ * Set the shardingStrategy value
*
* @param string $value - shardingStrategy value
*
diff --git a/lib/ArangoDBClient/CollectionHandler.php b/lib/ArangoDBClient/CollectionHandler.php
index adab99ca..d8948bf7 100644
--- a/lib/ArangoDBClient/CollectionHandler.php
+++ b/lib/ArangoDBClient/CollectionHandler.php
@@ -238,6 +238,7 @@ class CollectionHandler extends Handler
*
'distributeShardsLike' - name of prototype collection for identical sharding.
*
'numberOfShards' - number of shards for the collection.
*
'replicationFactor' - number of replicas to keep (default: 1).
+ *
'minReplicationFactor' - minimum number of replicas to be successful when writing (default: 1).
*
'shardKeys' - array of shard key attributes.
*
'shardingStrategy' - sharding strategy to use in cluster.
*
'smartJoinAttribute' - attribute name for smart joins (if not shard key).
@@ -295,6 +296,10 @@ public function create($collection, array $options = [])
$params[Collection::ENTRY_REPLICATION_FACTOR] = $collection->getReplicationFactor();
}
+ if ($collection->getMinReplicationFactor() !== null) {
+ $params[Collection::ENTRY_MIN_REPLICATION_FACTOR] = $collection->getMinReplicationFactor();
+ }
+
if ($collection->getShardingStrategy() !== null) {
$params[Collection::ENTRY_SHARDING_STRATEGY] = $collection->getShardingStrategy();
}
diff --git a/tests/CollectionBasicTest.php b/tests/CollectionBasicTest.php
index 6ffd8453..6b408447 100644
--- a/tests/CollectionBasicTest.php
+++ b/tests/CollectionBasicTest.php
@@ -314,6 +314,7 @@ public function testCreateCollectionWithReplicationFactor1()
}
$collection->setName($name);
+ $collection->setMinReplicationFactor(1);
$collection->setReplicationFactor(1);
$response = $collectionHandler->create($collection);
@@ -322,6 +323,7 @@ public function testCreateCollectionWithReplicationFactor1()
$properties = $resultingCollection->getAll();
static::assertEquals(1, $properties[Collection::ENTRY_REPLICATION_FACTOR]);
+ static::assertEquals(1, $properties[Collection::ENTRY_MIN_REPLICATION_FACTOR]);
}
@@ -349,6 +351,7 @@ public function testCreateCollectionWithReplicationFactor2()
}
$collection->setName($name);
+ $collection->setMinReplicationFactor(2);
$collection->setReplicationFactor(2);
$response = $collectionHandler->create($collection);
@@ -357,6 +360,7 @@ public function testCreateCollectionWithReplicationFactor2()
$properties = $resultingCollection->getAll();
static::assertEquals(2, $properties[Collection::ENTRY_REPLICATION_FACTOR]);
+ static::assertEquals(2, $properties[Collection::ENTRY_MIN_REPLICATION_FACTOR]);
}
/**
@@ -397,6 +401,7 @@ public function testCreateCollectionWithReplicationFactorSatellite()
$properties = $resultingCollection->getAll();
static::assertEquals("satellite", $properties[Collection::ENTRY_REPLICATION_FACTOR]);
+ static::assertEquals(0, $properties[Collection::ENTRY_MIN_REPLICATION_FACTOR]);
}
From edc731acbfafe69626ba7846755514a85dfd1f8f Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Fri, 2 Aug 2019 17:58:35 +0200
Subject: [PATCH 015/101] refactor Transaction.php for streaming transactions
---
CHANGELOG.md | 55 ++
examples/init.php | 2 +-
examples/transaction-query.php | 61 ++
examples/transaction.php | 61 ++
lib/ArangoDBClient/CollectionHandler.php | 21 +-
lib/ArangoDBClient/DocumentHandler.php | 45 +-
lib/ArangoDBClient/Handler.php | 13 +
lib/ArangoDBClient/Statement.php | 29 +-
lib/ArangoDBClient/StreamingTransaction.php | 158 ++++
.../StreamingTransactionCollection.php | 102 +++
.../StreamingTransactionHandler.php | 171 +++++
lib/ArangoDBClient/Transaction.php | 245 +------
lib/ArangoDBClient/TransactionBase.php | 397 ++++++++++
lib/ArangoDBClient/ValueValidator.php | 4 +
tests/StreamingTransactionTest.php | 682 ++++++++++++++++++
tests/TransactionTest.php | 32 +-
16 files changed, 1811 insertions(+), 267 deletions(-)
create mode 100644 examples/transaction-query.php
create mode 100644 examples/transaction.php
create mode 100644 lib/ArangoDBClient/StreamingTransaction.php
create mode 100644 lib/ArangoDBClient/StreamingTransactionCollection.php
create mode 100644 lib/ArangoDBClient/StreamingTransactionHandler.php
create mode 100644 lib/ArangoDBClient/TransactionBase.php
create mode 100644 tests/StreamingTransactionTest.php
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7c0ca608..2c7062fc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,61 @@
Release notes for the ArangoDB-PHP driver 3.5.x
===============================================
+Added support for streaming transactions (i.e. transactions that can be composed of multiple
+operations on the client side piece-by-piece without specifying the full transaction operations
+in advance).
+
+Streaming transactions currently support the following operations:
+
+- fetch documents by id, i.e. `DocumentHandler::getById()`
+- update documents by id, i.e. `DocumentHandler::updateById()`
+- replace documents by id, i.e. `DocumentHandler::replaceById()`
+- remove documents by id, i.e. `DocumentHandler::removeById()`
+- insert documents, i.e. `DocumentHandler::insert()`
+- counting documents in a collection, i.e. `CollectionHandler::count()`
+- truncating a collection, i.e. `CollectionHandler::truncate()`
+- running AQL queries, i.e. `Statement::execute()`
+
+Streaming transactions are provided by a new class `StreamingTransaction` and a new handler
+`StreamingTransactionHandler`.
+
+ $document = new DocumentHandler($connection);
+ $transactionHandler = new StreamingTransactionHandler($connection);
+
+ // creates a transaction object
+ $trx = new StreamingTransaction($connection, [
+ TransactionBase::ENTRY_COLLECTIONS => [
+ TransactionBase::ENTRY_WRITE => [ 'testCollection' ]
+ ]
+ ]);
+
+ // starts the transaction
+ $trx = $transactionHandler->create($trx);
+
+ // get a StreamingTransactionCollection object. this is used to execute operations
+ // in a transaction context
+ $trxCollection = $trx->getCollection('testCollection');
+
+ // pass the StreamingTransactionCollection into the document operations instead of
+ // a regular Collection object - this will make the operations execute in the context
+ // of the currently running transaction
+ $result = $documentHandler->insert($trxCollection, [ '_key' => 'test1', 'value' => 'test1' ]);
+
+ $result = $documentHandler->insert($trxCollection, [ '_key' => 'test2', 'value' => 'test2' ]);
+
+ // commits the transaction
+ $transactionHandler->commit($trx);
+
+Caveat: streaming transactions will normally stay open on the server side until they are explicitly
+aborted or committed by the client application, or until they time out automatically on the server.
+Therefore by default the PHP driver will automatically keep track of all begun streaming transactions,
+via an instance variable in the `StreamingTransactionHandler`.
+
+Streaming transactions are automatically aborted on shutdown via a shutdown function, and all
+transactions started via `StreamingTransactionHandler` instances that were neither committed nor
+aborted by the user will be aborted.
+
+
The `CollectionHandler` class got a new method `createTtlIndex` for creating time-to-live (TTL)
indexes on the server.
diff --git a/examples/init.php b/examples/init.php
index 7276acc8..79860c59 100644
--- a/examples/init.php
+++ b/examples/init.php
@@ -51,7 +51,7 @@
ConnectionOptions::OPTION_AUTH_PASSWD => '', // password for basic authorization
ConnectionOptions::OPTION_TIMEOUT => 30, // timeout in seconds
- ConnectionOptions::OPTION_TRACE => $traceFunc, // tracer function, can be used for debugging
+ // ConnectionOptions::OPTION_TRACE => $traceFunc, // tracer function, can be used for debugging
ConnectionOptions::OPTION_CREATE => false, // do not create unknown collections automatically
ConnectionOptions::OPTION_UPDATE_POLICY => UpdatePolicy::LAST, // last update wins
];
diff --git a/examples/transaction-query.php b/examples/transaction-query.php
new file mode 100644
index 00000000..7d03b21b
--- /dev/null
+++ b/examples/transaction-query.php
@@ -0,0 +1,61 @@
+create($collection);
+ } catch (\Exception $e) {
+ // collection may already exist - ignore this error for now
+ }
+
+ // clear everything, so we can start with a clean state
+ $collectionHandler->truncate($collection);
+
+ // creates a transaction object
+ $trx = new StreamingTransaction($connection, [
+ TransactionBase::ENTRY_COLLECTIONS => [
+ TransactionBase::ENTRY_WRITE => 'users'
+ ]
+ ]);
+
+ // starts the transaction
+ $trx = $transactionHandler->create($trx);
+
+ // get a StreamingTransactionCollection object. this is used to execute operations
+ // in a transaction context
+ $trxCollection = $trx->getCollection('users');
+
+ // calling query() directly on the transaction makes the AQL query execute in the
+ // context of the running transaction
+ $result = $trx->query([
+ 'query' => 'FOR i IN 1..10 INSERT { _key: CONCAT("test", i), value: i } INTO @@collection',
+ 'bindVars' => [ '@collection' => $trxCollection->getName() ]
+ ]);
+
+ echo "BEFORE COMMIT" . PHP_EOL;
+ echo "COLLECTION COUNT OUTSIDE OF TRANSACTION IS: ", $collectionHandler->count($collection) . PHP_EOL;
+ echo "COLLECTION COUNT INSIDE OF TRANSACTION IS: ", $collectionHandler->count($trxCollection) . PHP_EOL;
+
+ // commits the transaction
+ $transactionHandler->commit($trx);
+
+ echo PHP_EOL;
+ echo "AFTER COMMIT" . PHP_EOL;
+ echo "COLLECTION COUNT IS: ", $collectionHandler->count($collection) . PHP_EOL;
+} catch (ConnectException $e) {
+ print $e . PHP_EOL;
+} catch (ServerException $e) {
+ print $e . PHP_EOL;
+} catch (ClientException $e) {
+ print $e . PHP_EOL;
+}
diff --git a/examples/transaction.php b/examples/transaction.php
new file mode 100644
index 00000000..01e8191c
--- /dev/null
+++ b/examples/transaction.php
@@ -0,0 +1,61 @@
+create($collection);
+ } catch (\Exception $e) {
+ // collection may already exist - ignore this error for now
+ }
+
+ // clear everything, so we can start with a clean state
+ $collectionHandler->truncate($collection);
+
+ // creates a transaction object
+ $trx = new StreamingTransaction($connection, [
+ TransactionBase::ENTRY_COLLECTIONS => [
+ TransactionBase::ENTRY_WRITE => 'users'
+ ]
+ ]);
+
+ // starts the transaction
+ $trx = $transactionHandler->create($trx);
+
+ // get a StreamingTransactionCollection object. this is used to execute operations
+ // in a transaction context
+ $trxCollection = $trx->getCollection('users');
+
+ // pass the StreamingTransactionCollection into the document operations instead of
+ // a regular Collection object - this will make the operations execute in the context
+ // of the currently running transaction
+ $documentHandler->insert($trxCollection, [ '_key' => 'test1', 'value' => 'test1' ]);
+
+ $documentHandler->insert($trxCollection, [ '_key' => 'test2', 'value' => 'test2' ]);
+
+ echo "BEFORE COMMIT" . PHP_EOL;
+ echo "COLLECTION COUNT OUTSIDE OF TRANSACTION IS: ", $collectionHandler->count($collection) . PHP_EOL;
+ echo "COLLECTION COUNT INSIDE OF TRANSACTION IS: ", $collectionHandler->count($trxCollection) . PHP_EOL;
+
+ // commits the transaction
+ $transactionHandler->commit($trx);
+
+ echo PHP_EOL;
+ echo "AFTER COMMIT" . PHP_EOL;
+ echo "COLLECTION COUNT IS: ", $collectionHandler->count($collection) . PHP_EOL;
+} catch (ConnectException $e) {
+ print $e . PHP_EOL;
+} catch (ServerException $e) {
+ print $e . PHP_EOL;
+} catch (ClientException $e) {
+ print $e . PHP_EOL;
+}
diff --git a/lib/ArangoDBClient/CollectionHandler.php b/lib/ArangoDBClient/CollectionHandler.php
index d8948bf7..737d1ae4 100644
--- a/lib/ArangoDBClient/CollectionHandler.php
+++ b/lib/ArangoDBClient/CollectionHandler.php
@@ -393,9 +393,12 @@ public function has($collection)
*/
public function count($collection)
{
+ $headers = [];
+ $this->addTransactionHeader($headers, $collection);
+
$collection = $this->makeCollection($collection);
$url = UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collection, self::OPTION_COUNT]);
- $response = $this->getConnection()->get($url);
+ $response = $this->getConnection()->get($url, $headers);
$data = $response->getJson();
$count = $data[self::OPTION_COUNT];
@@ -616,15 +619,19 @@ public function unload($collection)
*/
public function truncate($collection)
{
- $collectionId = $this->getCollectionId($collection);
-
- if ($this->isValidCollectionId($collectionId)) {
- throw new ClientException('Cannot alter a collection without a collection id');
+ $headers = [];
+ $bodyParams = [];
+ $this->addTransactionHeader($headers, $collection);
+ if ($collection instanceof StreamingTransactionCollection) {
+ $bodyParams['transactionId'] = $collection->getTrxId();
}
+ $collection = $this->makeCollection($collection);
+
$this->getConnection()->put(
- UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collectionId, self::OPTION_TRUNCATE]),
- ''
+ UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collection, self::OPTION_TRUNCATE]),
+ $this->json_encode_wrapper($bodyParams),
+ $headers
);
return true;
diff --git a/lib/ArangoDBClient/DocumentHandler.php b/lib/ArangoDBClient/DocumentHandler.php
index e2b05fd4..82ed19a7 100644
--- a/lib/ArangoDBClient/DocumentHandler.php
+++ b/lib/ArangoDBClient/DocumentHandler.php
@@ -170,19 +170,21 @@ public function getById($collection, $documentId, array $options = [])
*/
protected function getDocument($url, $collection, $documentId, array $options = [])
{
- $collection = $this->makeCollection($collection);
+ $headers = [];
+ $this->addTransactionHeader($headers, $collection);
+
+ $collection = $this->makeCollection($collection);
$url = UrlHelper::buildUrl($url, [$collection, $documentId]);
- $headerElements = [];
if (array_key_exists('ifMatch', $options) && array_key_exists('revision', $options)) {
if ($options['ifMatch'] === true) {
- $headerElements['If-Match'] = '"' . $options['revision'] . '"';
+ $headers['If-Match'] = '"' . $options['revision'] . '"';
} else {
- $headerElements['If-None-Match'] = '"' . $options['revision'] . '"';
+ $headers['If-None-Match'] = '"' . $options['revision'] . '"';
}
}
- $response = $this->getConnection()->get($url, $headerElements);
+ $response = $this->getConnection()->get($url, $headers);
if ($response->getHttpCode() === 304) {
throw new ClientException('Document has not changed.');
@@ -232,19 +234,21 @@ public function getHead($collection, $documentId, $revision = null, $ifMatch = n
*/
protected function head($url, $collection, $documentId, $revision = null, $ifMatch = null)
{
- $collection = $this->makeCollection($collection);
+ $headers = [];
+ $this->addTransactionHeader($headers, $collection);
+
+ $collection = $this->makeCollection($collection);
$url = UrlHelper::buildUrl($url, [$collection, $documentId]);
- $headerElements = [];
if ($revision !== null && $ifMatch !== null) {
if ($ifMatch) {
- $headerElements['If-Match'] = '"' . $revision . '"';
+ $headers['If-Match'] = '"' . $revision . '"';
} else {
- $headerElements['If-None-Match'] = '"' . $revision . '"';
+ $headers['If-None-Match'] = '"' . $revision . '"';
}
}
-
- $response = $this->getConnection()->head($url, $headerElements);
+
+ $response = $this->getConnection()->head($url, $headers);
$headers = $response->getHeaders();
$headers['httpCode'] = $response->getHttpCode();
@@ -337,6 +341,9 @@ public function store(Document $document, $collection = null, array $options = [
*/
public function save($collection, $document, array $options = [])
{
+ $headers = [];
+ $this->addTransactionHeader($headers, $collection);
+
$collection = $this->makeCollection($collection);
$_documentClass = $this->_documentClass;
@@ -361,7 +368,7 @@ public function save($collection, $document, array $options = [])
$data = $document->getAllForInsertUpdate();
}
- $response = $this->getConnection()->post($url, $this->json_encode_wrapper($data));
+ $response = $this->getConnection()->post($url, $this->json_encode_wrapper($data), $headers);
$json = $response->getJson();
// 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 $
*/
protected function patch($url, $collection, $documentId, Document $document, array $options = [])
{
+ $headers = [];
+ $this->addTransactionHeader($headers, $collection);
+
$collection = $this->makeCollection($collection);
$_documentClass = $this->_documentClass;
@@ -509,7 +519,6 @@ protected function patch($url, $collection, $documentId, Document $document, arr
);
- $headers = [];
if (isset($params[ConnectionOptions::OPTION_UPDATE_POLICY]) &&
$params[ConnectionOptions::OPTION_UPDATE_POLICY] === UpdatePolicy::ERROR
) {
@@ -618,6 +627,9 @@ public function replaceById($collection, $documentId, Document $document, array
*/
protected function put($url, $collection, $documentId, Document $document, array $options = [])
{
+ $headers = [];
+ $this->addTransactionHeader($headers, $collection);
+
$collection = $this->makeCollection($collection);
$_documentClass = $this->_documentClass;
@@ -632,7 +644,6 @@ protected function put($url, $collection, $documentId, Document $document, array
]
);
- $headers = [];
if (isset($params[ConnectionOptions::OPTION_REPLACE_POLICY]) &&
$params[ConnectionOptions::OPTION_REPLACE_POLICY] === UpdatePolicy::ERROR
) {
@@ -641,7 +652,7 @@ protected function put($url, $collection, $documentId, Document $document, array
$headers['if-match'] = '"' . $options['revision'] . '"';
}
}
-
+
$data = $document->getAllForInsertUpdate();
$url = UrlHelper::buildUrl($url, [$collection, $documentId]);
@@ -725,6 +736,9 @@ public function removeById($collection, $documentId, $revision = null, array $op
*/
protected function erase($url, $collection, $documentId, $revision = null, array $options = [])
{
+ $headers = [];
+ $this->addTransactionHeader($headers, $collection);
+
$collection = $this->makeCollection($collection);
$params = $this->includeOptionsInParams(
@@ -737,7 +751,6 @@ protected function erase($url, $collection, $documentId, $revision = null, array
]
);
- $headers = [];
if (isset($params[ConnectionOptions::OPTION_DELETE_POLICY]) &&
$params[ConnectionOptions::OPTION_DELETE_POLICY] === UpdatePolicy::ERROR
) {
diff --git a/lib/ArangoDBClient/Handler.php b/lib/ArangoDBClient/Handler.php
index c1321e69..e772dc58 100644
--- a/lib/ArangoDBClient/Handler.php
+++ b/lib/ArangoDBClient/Handler.php
@@ -174,6 +174,19 @@ protected function makeCollection($value)
return $value;
}
+
+ /**
+ * Add a transaction header to the array of headers in case this is a transactional operation
+ *
+ * @param array $headers - already existing headers
+ * @param mixed $collection - any type of collection (can be StreamingTransactionCollection or other)
+ */
+ protected function addTransactionHeader(array &$headers, $collection)
+ {
+ if ($collection instanceof StreamingTransactionCollection) {
+ $headers['x-arango-trx-id'] = $collection->getTrxId();
+ }
+ }
}
class_alias(Handler::class, '\triagens\ArangoDb\Handler');
diff --git a/lib/ArangoDBClient/Statement.php b/lib/ArangoDBClient/Statement.php
index ab1cea0b..c071d205 100644
--- a/lib/ArangoDBClient/Statement.php
+++ b/lib/ArangoDBClient/Statement.php
@@ -149,6 +149,13 @@ class Statement
* @var int
*/
private $_memoryLimit = 0;
+
+ /**
+ * transaction id (used internally)
+ *
+ * @var string
+ */
+ private $_trxId = null;
/**
* resultType
@@ -157,7 +164,6 @@ class Statement
*/
private $resultType;
-
/**
* Query string index
*/
@@ -208,6 +214,11 @@ class Statement
*/
const ENTRY_TTL = 'ttl';
+ /**
+ * transaction attribute (used internally)
+ */
+ const ENTRY_TRANSACTION = 'transaction';
+
/**
* Initialise the statement
*
@@ -284,6 +295,10 @@ public function __construct(Connection $connection, array $data)
if (isset($data[self::ENTRY_MEMORY_LIMIT])) {
$this->_memoryLimit = (int) $data[self::ENTRY_MEMORY_LIMIT];
}
+
+ if (isset($data[self::ENTRY_TRANSACTION]) && $data[self::ENTRY_TRANSACTION] instanceof StreamingTransaction) {
+ $this->_trxId = $data[self::ENTRY_TRANSACTION]->getId();
+ }
}
/**
@@ -311,12 +326,18 @@ public function execute()
throw new ClientException('Query should be a string');
}
- $data = $this->buildData();
+ $data = $this->buildData();
+
+ $headers = [];
+ if ($this->_trxId) {
+ // inject transaction id
+ $headers['x-arango-trx-id'] = $this->_trxId;
+ }
- $tries = 0;
+ $tries = 0;
while (true) {
try {
- $response = $this->_connection->post(Urls::URL_CURSOR, $this->getConnection()->json_encode_wrapper($data), []);
+ $response = $this->_connection->post(Urls::URL_CURSOR, $this->getConnection()->json_encode_wrapper($data), $headers);
return new Cursor($this->_connection, $response->getJson(), $this->getCursorOptions());
} catch (ServerException $e) {
diff --git a/lib/ArangoDBClient/StreamingTransaction.php b/lib/ArangoDBClient/StreamingTransaction.php
new file mode 100644
index 00000000..1511c7bf
--- /dev/null
+++ b/lib/ArangoDBClient/StreamingTransaction.php
@@ -0,0 +1,158 @@
+buildTransactionAttributesFromArray($transactionArray);
+ }
+
+ // set up participating collections
+ $this->_collections = [];
+ foreach ($this->attributes[self::ENTRY_COLLECTIONS][self::ENTRY_EXCLUSIVE] as $name) {
+ $this->_collections[$name] = new StreamingTransactionCollection($this, $name, self::ENTRY_EXCLUSIVE);
+ }
+
+ foreach ($this->attributes[self::ENTRY_COLLECTIONS][self::ENTRY_WRITE] as $name) {
+ if (!isset($this->_collections[$name])) {
+ $this->_collections[$name] = new StreamingTransactionCollection($this, $name, self::ENTRY_WRITE);
+ }
+ }
+
+ foreach ($this->attributes[self::ENTRY_COLLECTIONS][self::ENTRY_READ] as $name) {
+ if (!isset($this->_collections[$name])) {
+ $this->_collections[$name] = new StreamingTransactionCollection($this, $name, self::ENTRY_READ);
+ }
+ }
+ }
+
+
+ /**
+ * Get a participating collection of the transaction by name
+ * Will throw an exception if the collection is not part of the transaction
+ *
+ * @throws ClientException
+ *
+ * @param string $name - name of the collection
+ *
+ * @return StreamingTransactionCollection - collection object
+ */
+ public function getCollection($name)
+ {
+ if (!isset($this->_collections[$name])) {
+ throw new ClientException('Collection not registered in transaction');
+ }
+ return $this->_collections[$name];
+ }
+
+ /**
+ * Get the transaction's id
+ *
+ * @return string - transaction id
+ */
+ public function getId()
+ {
+ return $this->_id;
+ }
+
+ /**
+ * Set the transaction's id - this is used internally and should not be called by end users
+ *
+ * @param mixed $id - transaction id as number or string
+ */
+ public function setId($id)
+ {
+ assert(is_string($id) || is_numeric($id));
+
+ if ($this->_id !== null) {
+ throw new ClientException('Should not update the id of an existing transaction');
+ }
+
+ if (is_numeric($id)) {
+ $id = (string) $id;
+ }
+ $this->_id = $id;
+ }
+
+
+ /**
+ * Executes an AQL query inside the transaction
+ *
+ * This is a shortcut for creating a new Statement and executing it.
+ *
+ * @throws ClientException
+ *
+ * @param array $data - query data, as is required by Statement::__construct()
+ *
+ * @return Cursor - query cursor, as returned by Statement::execute()
+ */
+ public function query(array $data)
+ {
+ $data['transaction'] = $this;
+ $statement = new Statement($this->getConnection(), $data);
+ return $statement->execute();
+ }
+
+ /**
+ * Build the object's attributes from a given array
+ *
+ * @param $options
+ *
+ * @throws \ArangoDBClient\ClientException
+ */
+ protected function buildTransactionAttributesFromArray($options)
+ {
+ parent::buildTransactionAttributesFromArray($options);
+
+ if (isset($options[self::ENTRY_ID])) {
+ $this->setId($options[self::ENTRY_ID]);
+ }
+ }
+
+}
+
+class_alias(Transaction::class, '\triagens\ArangoDb\StreamingTransaction');
diff --git a/lib/ArangoDBClient/StreamingTransactionCollection.php b/lib/ArangoDBClient/StreamingTransactionCollection.php
new file mode 100644
index 00000000..b48c4cb7
--- /dev/null
+++ b/lib/ArangoDBClient/StreamingTransactionCollection.php
@@ -0,0 +1,102 @@
+_trx = $trx;
+ $this->_name = $name;
+ $this->_mode = $mode;
+ }
+
+ /**
+ * Return the name of the collection
+ *
+ * @magic
+ *
+ * @return string - collection name
+ */
+ public function __toString()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * Return the name of the collection
+ *
+ * @return string - collection name
+ */
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * Return the lock mode of the collection
+ *
+ * @return string - lock mode, i.e. 'read', 'write', 'exclusive'
+ */
+ public function getMode()
+ {
+ return $this->_mode;
+ }
+
+ /**
+ * Return the transaction's id
+ *
+ * @return string - transaction id
+ */
+ public function getTrxId()
+ {
+ return $this->_trx->getId();
+ }
+}
+
+class_alias(Transaction::class, '\triagens\ArangoDb\StreamingTransactionCollection');
diff --git a/lib/ArangoDBClient/StreamingTransactionHandler.php b/lib/ArangoDBClient/StreamingTransactionHandler.php
new file mode 100644
index 00000000..50d431e1
--- /dev/null
+++ b/lib/ArangoDBClient/StreamingTransactionHandler.php
@@ -0,0 +1,171 @@
+getConnection());
+ }
+
+ $response = $this->getConnection()->post(
+ Urls::URL_TRANSACTION . '/begin',
+ $this->getConnection()->json_encode_wrapper($trx->attributes)
+ );
+
+ $jsonResponse = $response->getJson();
+ $id = $jsonResponse['result']['id'];
+ $trx->setId($id);
+
+ $this->_pendingTransactions[$id] = true;
+ return $trx;
+ }
+
+ /**
+ * Closes all pending transactions created by the handler
+ */
+ public function closePendingTransactions()
+ {
+ // automatically abort all unintentionally kept-open transactions, so we don't
+ // leak server resources by not closing transactions
+ foreach ($this->_pendingTransactions as $id => $done) {
+ try {
+ $this->abort($id);
+ } catch (\Exception $e) {
+ // ignore all errors here
+ }
+ }
+ $this->_pendingTransactions = [];
+ }
+
+ /**
+ * Retrieves the status of a transaction
+ *
+ * @throws ServerException
+ *
+ * @param mixed $trx - streaming transaction object or transaction id as string
+ *
+ * @return array - returns an array with attributes 'id' and 'status'
+ */
+ public function getStatus($trx)
+ {
+ if ($trx instanceof StreamingTransaction) {
+ $id = $trx->getId();
+ } else {
+ $id = (string) $trx;
+ }
+
+ $response = $this->getConnection()->get(UrlHelper::buildUrl(Urls::URL_TRANSACTION, [$id]));
+ $jsonResponse = $response->getJson();
+
+ $status = $jsonResponse['result']['status'];
+ if ($status === 'aborted' || $status === 'committed') {
+ unset($this->_pendingTransactions[$id]);
+ }
+
+ return $jsonResponse['result'];
+ }
+
+ /**
+ * Commits a transaction
+ *
+ * @throws ServerException
+ *
+ * @param mixed $trx - streaming transaction object or transaction id as string
+ *
+ * @return bool - true if commit succeeds, throws an exception otherwise
+ */
+ public function commit($trx)
+ {
+ if ($trx instanceof StreamingTransaction) {
+ $id = $trx->getId();
+ } else {
+ $id = (string) $trx;
+ }
+
+ unset($this->_pendingTransactions[$id]);
+ $this->getConnection()->put(UrlHelper::buildUrl(Urls::URL_TRANSACTION, [$id]), '');
+
+ return true;
+ }
+
+ /**
+ * Aborts a transaction
+ *
+ * @throws ServerException
+ *
+ * @param mixed $trx - streaming transaction object or transaction id as string
+ *
+ * @return bool - true if abort succeeds, throws an exception otherwise
+ */
+ public function abort($trx)
+ {
+ if ($trx instanceof StreamingTransaction) {
+ $id = $trx->getId();
+ } else {
+ $id = (string) $trx;
+ }
+
+ unset($this->_pendingTransactions[$id]);
+ $this->getConnection()->delete(UrlHelper::buildUrl(Urls::URL_TRANSACTION, [$id]));
+
+ return true;
+ }
+
+ /**
+ * Return all currently running transactions
+ *
+ * @throws ServerException
+ *
+ * @return array - array of currently running transactions, each transaction is an array with attributes 'id' and 'status'
+ */
+ public function getRunning()
+ {
+ $response = $this->getConnection()->get(Urls::URL_TRANSACTION);
+
+ $jsonResponse = $response->getJson();
+ return $jsonResponse['transactions'];
+ }
+
+}
+
+class_alias(CollectionHandler::class, '\triagens\ArangoDb\StreamingTransactionHandler');
diff --git a/lib/ArangoDBClient/Transaction.php b/lib/ArangoDBClient/Transaction.php
index 674fa850..ec18a26a 100644
--- a/lib/ArangoDBClient/Transaction.php
+++ b/lib/ArangoDBClient/Transaction.php
@@ -40,8 +40,9 @@
*
* There are also helper functions to set collections directly, based on their locking:
*
- * $this->setWriteCollections($array or $string if single collection)
* $this->setReadCollections($array or $string if single collection)
+ * $this->setWriteCollections($array or $string if single collection)
+ * $this->setExclusiveCollections($array or $string if single collection)
*
*
*
@@ -55,57 +56,18 @@
* @package ArangoDBClient
* @since 1.3
*/
-class Transaction
+class Transaction extends TransactionBase
{
- /**
- * The connection object
- *
- * @var Connection
- */
- private $_connection;
-
- /**
- * The transaction's attributes.
- *
- * @var array
- */
- protected $attributes = [];
-
- /**
- * Collections index
- */
- const ENTRY_COLLECTIONS = 'collections';
-
/**
* Action index
*/
const ENTRY_ACTION = 'action';
- /**
- * WaitForSync index
- */
- const ENTRY_WAIT_FOR_SYNC = 'waitForSync';
-
- /**
- * Lock timeout index
- */
- const ENTRY_LOCK_TIMEOUT = 'lockTimeout';
-
/**
* Params index
*/
const ENTRY_PARAMS = 'params';
- /**
- * Read index
- */
- const ENTRY_READ = 'read';
-
- /**
- * WRITE index
- */
- const ENTRY_WRITE = 'write';
-
/**
* @var $_action string The action property of the transaction.
*/
@@ -136,7 +98,8 @@ class Transaction
*/
public function __construct(Connection $connection, array $transactionArray = null)
{
- $this->_connection = $connection;
+ parent::__construct($connection);
+
if (is_array($transactionArray)) {
$this->buildTransactionAttributesFromArray($transactionArray);
}
@@ -154,7 +117,7 @@ public function __construct(Connection $connection, array $transactionArray = nu
*/
public function execute()
{
- $response = $this->_connection->post(
+ $response = $this->getConnection()->post(
Urls::URL_TRANSACTION,
$this->getConnection()->json_encode_wrapper($this->attributes)
);
@@ -167,49 +130,6 @@ public function execute()
}
- /**
- * Return the connection object
- *
- * @return Connection - the connection object
- */
- protected function getConnection()
- {
- return $this->_connection;
- }
-
-
- /**
- * Set the collections array.
- *
- * The array should have 2 sub-arrays, namely 'read' and 'write' which should hold the respective collections
- * for the transaction
- *
- * @param array $value
- */
- public function setCollections(array $value)
- {
- if (array_key_exists('read', $value)) {
- $this->setReadCollections($value['read']);
- }
- if (array_key_exists('write', $value)) {
- $this->setWriteCollections($value['write']);
- }
- }
-
-
- /**
- * Get collections array
- *
- * This holds the read and write collections of the transaction
- *
- * @return array $value
- */
- public function getCollections()
- {
- return $this->get(self::ENTRY_COLLECTIONS);
- }
-
-
/**
* set action value
*
@@ -234,54 +154,6 @@ public function getAction()
}
- /**
- * set waitForSync value
- *
- * @param bool $value
- *
- * @throws \ArangoDBClient\ClientException
- */
- public function setWaitForSync($value)
- {
- $this->set(self::ENTRY_WAIT_FOR_SYNC, (bool) $value);
- }
-
-
- /**
- * get waitForSync value
- *
- * @return bool waitForSync
- */
- public function getWaitForSync()
- {
- return $this->get(self::ENTRY_WAIT_FOR_SYNC);
- }
-
-
- /**
- * Set lockTimeout value
- *
- * @param int $value
- *
- * @throws \ArangoDBClient\ClientException
- */
- public function setLockTimeout($value)
- {
- $this->set(self::ENTRY_LOCK_TIMEOUT, (int) $value);
- }
-
-
- /**
- * Get lockTimeout value
- *
- * @return int lockTimeout
- */
- public function getLockTimeout()
- {
- return $this->get(self::ENTRY_LOCK_TIMEOUT);
- }
-
-
/**
* Set params value
*
@@ -306,56 +178,6 @@ public function getParams()
}
- /**
- * Convenience function to directly set write-collections without having to access
- * them from the collections attribute.
- *
- * @param array $value
- */
- public function setWriteCollections($value)
- {
-
- $this->attributes[self::ENTRY_COLLECTIONS][self::ENTRY_WRITE] = $value;
- }
-
-
- /**
- * Convenience function to directly get write-collections without having to access
- * them from the collections attribute.
- *
- * @return array params
- */
- public function getWriteCollections()
- {
- return $this->attributes[self::ENTRY_COLLECTIONS][self::ENTRY_WRITE];
- }
-
-
- /**
- * Convenience function to directly set read-collections without having to access
- * them from the collections attribute.
- *
- * @param array $value
- */
- public function setReadCollections($value)
- {
-
- $this->attributes[self::ENTRY_COLLECTIONS][self::ENTRY_READ] = $value;
- }
-
-
- /**
- * Convenience function to directly get read-collections without having to access
- * them from the collections attribute.
- *
- * @return array params
- */
- public function getReadCollections()
- {
- return $this->attributes[self::ENTRY_COLLECTIONS][self::ENTRY_READ];
- }
-
-
/**
* Sets an attribute
*
@@ -392,57 +214,16 @@ public function set($key, $value)
public function __set($key, $value)
{
switch ($key) {
- case self::ENTRY_COLLECTIONS :
- $this->setCollections($value);
- break;
- case 'writeCollections' :
- $this->setWriteCollections($value);
- break;
- case 'readCollections' :
- $this->setReadCollections($value);
- break;
case self::ENTRY_ACTION :
$this->setAction($value);
break;
- case self::ENTRY_WAIT_FOR_SYNC :
- $this->setWaitForSync($value);
- break;
- case self::ENTRY_LOCK_TIMEOUT :
- $this->setLockTimeout($value);
- break;
case self::ENTRY_PARAMS :
$this->setParams($value);
break;
default:
- $this->set($key, $value);
- break;
- }
- }
-
-
- /**
- * Get an attribute
- *
- * @param string $key - name of attribute
- *
- * @return mixed - value of attribute, NULL if attribute is not set
- */
- public function get($key)
- {
- switch ($key) {
- case 'writeCollections' :
- return $this->getWriteCollections();
- break;
- case 'readCollections' :
- return $this->getReadCollections();
+ parent::__set($key, $value);
break;
}
-
- if (isset($this->attributes[$key])) {
- return $this->attributes[$key];
- }
-
- return null;
}
@@ -502,22 +283,12 @@ public function __toString()
*/
public function buildTransactionAttributesFromArray($options)
{
- if (isset($options[self::ENTRY_COLLECTIONS])) {
- $this->setCollections($options[self::ENTRY_COLLECTIONS]);
- }
+ parent::buildTransactionAttributesFromArray($options);
if (isset($options[self::ENTRY_ACTION])) {
$this->setAction($options[self::ENTRY_ACTION]);
}
- if (isset($options[self::ENTRY_WAIT_FOR_SYNC])) {
- $this->setWaitForSync($options[self::ENTRY_WAIT_FOR_SYNC]);
- }
-
- if (isset($options[self::ENTRY_LOCK_TIMEOUT])) {
- $this->setLockTimeout($options[self::ENTRY_LOCK_TIMEOUT]);
- }
-
if (isset($options[self::ENTRY_PARAMS])) {
$this->setParams($options[self::ENTRY_PARAMS]);
}
diff --git a/lib/ArangoDBClient/TransactionBase.php b/lib/ArangoDBClient/TransactionBase.php
new file mode 100644
index 00000000..f7e3006c
--- /dev/null
+++ b/lib/ArangoDBClient/TransactionBase.php
@@ -0,0 +1,397 @@
+_connection = $connection;
+
+ $this->attributes[self::ENTRY_COLLECTIONS] = [
+ self::ENTRY_READ => [],
+ self::ENTRY_WRITE => [],
+ self::ENTRY_EXCLUSIVE => []
+ ];
+ }
+
+
+ /**
+ * Return the connection object
+ *
+ * @return Connection - the connection object
+ */
+ protected function getConnection()
+ {
+ return $this->_connection;
+ }
+
+
+ /**
+ * Set the collections array.
+ *
+ * The array should have 2 sub-arrays, namely 'read' and 'write' which should hold the respective collections
+ * for the transaction
+ *
+ * @param array $value
+ */
+ public function setCollections(array $value)
+ {
+ if (array_key_exists('read', $value)) {
+ $this->setReadCollections($value['read']);
+ }
+ if (array_key_exists('write', $value)) {
+ $this->setWriteCollections($value['write']);
+ }
+ if (array_key_exists('exclusive', $value)) {
+ $this->setExclusiveCollections($value['exclusive']);
+ }
+ }
+
+
+ /**
+ * Get collections array
+ *
+ * This holds the read and write collections of the transaction
+ *
+ * @return array $value
+ */
+ public function getCollections()
+ {
+ return $this->get(self::ENTRY_COLLECTIONS);
+ }
+
+
+ /**
+ * set waitForSync value
+ *
+ * @param bool $value
+ *
+ * @throws \ArangoDBClient\ClientException
+ */
+ public function setWaitForSync($value)
+ {
+ $this->set(self::ENTRY_WAIT_FOR_SYNC, (bool) $value);
+ }
+
+
+ /**
+ * get waitForSync value
+ *
+ * @return bool waitForSync
+ */
+ public function getWaitForSync()
+ {
+ return $this->get(self::ENTRY_WAIT_FOR_SYNC);
+ }
+
+
+ /**
+ * Set lockTimeout value
+ *
+ * @param int $value
+ *
+ * @throws \ArangoDBClient\ClientException
+ */
+ public function setLockTimeout($value)
+ {
+ $this->set(self::ENTRY_LOCK_TIMEOUT, (int) $value);
+ }
+
+
+ /**
+ * Get lockTimeout value
+ *
+ * @return int lockTimeout
+ */
+ public function getLockTimeout()
+ {
+ return $this->get(self::ENTRY_LOCK_TIMEOUT);
+ }
+
+
+ /**
+ * Convenience function to directly set read-collections without having to access
+ * them from the collections attribute.
+ *
+ * @param array $value
+ */
+ public function setReadCollections($value)
+ {
+ $this->attributes[self::ENTRY_COLLECTIONS][self::ENTRY_READ] = (array) $value;
+ }
+
+
+ /**
+ * Convenience function to directly get read-collections without having to access
+ * them from the collections attribute.
+ *
+ * @return array params
+ */
+ public function getReadCollections()
+ {
+ return $this->attributes[self::ENTRY_COLLECTIONS][self::ENTRY_READ];
+ }
+
+
+ /**
+ * Convenience function to directly set write-collections without having to access
+ * them from the collections attribute.
+ *
+ * @param array $value
+ */
+ public function setWriteCollections($value)
+ {
+ $this->attributes[self::ENTRY_COLLECTIONS][self::ENTRY_WRITE] = (array) $value;
+ }
+
+
+ /**
+ * Convenience function to directly get write-collections without having to access
+ * them from the collections attribute.
+ *
+ * @return array params
+ */
+ public function getWriteCollections()
+ {
+ return $this->attributes[self::ENTRY_COLLECTIONS][self::ENTRY_WRITE];
+ }
+
+
+ /**
+ * Convenience function to directly set exclusive-collections without having to access
+ * them from the collections attribute.
+ *
+ * @param array $value
+ */
+ public function setExclusiveCollections($value)
+ {
+ $this->attributes[self::ENTRY_COLLECTIONS][self::ENTRY_EXCLUSIVE] = (array) $value;
+ }
+
+
+ /**
+ * Convenience function to directly get exclusive-collections without having to access
+ * them from the collections attribute.
+ *
+ * @return array params
+ */
+ public function getExclusiveCollections()
+ {
+ return $this->attributes[self::ENTRY_COLLECTIONS][self::ENTRY_EXCLUSIVE];
+ }
+
+
+ /**
+ * Sets an attribute
+ *
+ * @param $key
+ * @param $value
+ *
+ * @throws ClientException
+ */
+ public function set($key, $value)
+ {
+ if (!is_string($key)) {
+ throw new ClientException('Invalid document attribute key');
+ }
+
+ $this->attributes[$key] = $value;
+ }
+
+
+ /**
+ * Set an attribute, magic method
+ *
+ * This is a magic method that allows the object to be used without
+ * declaring all document attributes first.
+ *
+ * @throws ClientException
+ *
+ * @magic
+ *
+ * @param string $key - attribute name
+ * @param mixed $value - value for attribute
+ *
+ * @return void
+ */
+ public function __set($key, $value)
+ {
+ switch ($key) {
+ case self::ENTRY_COLLECTIONS :
+ $this->setCollections($value);
+ break;
+ case 'writeCollections' :
+ $this->setWriteCollections($value);
+ break;
+ case 'readCollections' :
+ $this->setReadCollections($value);
+ break;
+ case 'exclusiveCollections' :
+ $this->setExclusiveCollections($value);
+ break;
+ case self::ENTRY_WAIT_FOR_SYNC :
+ $this->setWaitForSync($value);
+ break;
+ case self::ENTRY_LOCK_TIMEOUT :
+ $this->setLockTimeout($value);
+ break;
+ default:
+ $this->set($key, $value);
+ break;
+ }
+ }
+
+
+ /**
+ * Get an attribute
+ *
+ * @param string $key - name of attribute
+ *
+ * @return mixed - value of attribute, NULL if attribute is not set
+ */
+ public function get($key)
+ {
+ switch ($key) {
+ case 'readCollections' :
+ return $this->getReadCollections();
+ break;
+ case 'writeCollections' :
+ return $this->getWriteCollections();
+ break;
+ case 'exclusiveCollections' :
+ return $this->getExclusiveCollections();
+ break;
+ }
+
+ if (isset($this->attributes[$key])) {
+ return $this->attributes[$key];
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Get an attribute, magic method
+ *
+ * This function is mapped to get() internally.
+ *
+ * @magic
+ *
+ * @param string $key - name of attribute
+ *
+ * @return mixed - value of attribute, NULL if attribute is not set
+ */
+ public function __get($key)
+ {
+ return $this->get($key);
+ }
+
+
+ /**
+ * Is triggered by calling isset() or empty() on inaccessible properties.
+ *
+ * @param string $key - name of attribute
+ *
+ * @return boolean returns true or false (set or not set)
+ */
+ public function __isset($key)
+ {
+ if (isset($this->attributes[$key])) {
+ return true;
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Build the object's attributes from a given array
+ *
+ * @param $options
+ *
+ * @throws \ArangoDBClient\ClientException
+ */
+ protected function buildTransactionAttributesFromArray($options)
+ {
+ if (isset($options[self::ENTRY_COLLECTIONS])) {
+ $this->setCollections($options[self::ENTRY_COLLECTIONS]);
+ }
+
+ if (isset($options[self::ENTRY_WAIT_FOR_SYNC])) {
+ $this->setWaitForSync($options[self::ENTRY_WAIT_FOR_SYNC]);
+ }
+
+ if (isset($options[self::ENTRY_LOCK_TIMEOUT])) {
+ $this->setLockTimeout($options[self::ENTRY_LOCK_TIMEOUT]);
+ }
+ }
+}
+
+class_alias(TransactionBase::class, '\triagens\ArangoDb\TransactionBase');
diff --git a/lib/ArangoDBClient/ValueValidator.php b/lib/ArangoDBClient/ValueValidator.php
index c948960e..d4044351 100644
--- a/lib/ArangoDBClient/ValueValidator.php
+++ b/lib/ArangoDBClient/ValueValidator.php
@@ -45,6 +45,10 @@ public static function validate($value)
return;
}
+ if ($value instanceof Collection) {
+ return;
+ }
+
// type is invalid
throw new ClientException('Invalid bind parameter value');
}
diff --git a/tests/StreamingTransactionTest.php b/tests/StreamingTransactionTest.php
new file mode 100644
index 00000000..d0a198bd
--- /dev/null
+++ b/tests/StreamingTransactionTest.php
@@ -0,0 +1,682 @@
+_shutdown = [];
+
+ $this->connection = getConnection();
+ $this->collectionHandler = new CollectionHandler($this->connection);
+
+ // clean up first
+ try {
+ $this->collectionHandler->drop('ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp);
+ } catch (\Exception $e) {
+ // don't bother us, if it's already deleted.
+ }
+
+ try {
+ $this->collectionHandler->drop('ArangoDB_PHP_TestSuite_TestCollection_02' . '_' . static::$testsTimestamp);
+ } catch (Exception $e) {
+ //Silence the exception
+ }
+
+
+ $this->collection1 = new Collection();
+ $this->collection1->setName('ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp);
+ $this->collectionHandler->create($this->collection1);
+
+ $this->collection2 = new Collection();
+ $this->collection2->setName('ArangoDB_PHP_TestSuite_TestCollection_02' . '_' . static::$testsTimestamp);
+ $this->collectionHandler->create($this->collection2);
+
+ $adminHandler = new AdminHandler($this->connection);
+ $this->isMMFilesEngine = ($adminHandler->getEngine()["name"] == "mmfiles");
+
+ $this->transactionHandler = new StreamingTransactionHandler($this->connection);
+ }
+
+
+ public function testCreateTransaction()
+ {
+ $trx = $this->transactionHandler->create();
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue(is_string($trx->getId()));
+
+ $status = $this->transactionHandler->getStatus($trx);
+
+ static::assertEquals($trx->getId(), $status['id']);
+ static::assertEquals('running', $status['status']);
+
+ $running = array_map(function($trx) { return $trx['id']; }, $this->transactionHandler->getRunning());
+ static::assertTrue(in_array($trx->getId(), $running));
+ }
+
+ public function testCreateAndAbortTransaction()
+ {
+ $trx = $this->transactionHandler->create();
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue(is_string($trx->getId()));
+
+ static::assertTrue($this->transactionHandler->abort($trx));
+ $status = $this->transactionHandler->getStatus($trx);
+
+ static::assertEquals($trx->getId(), $status['id']);
+ static::assertEquals('aborted', $status['status']);
+
+ $running = array_map(function($trx) { return $trx['id']; }, $this->transactionHandler->getRunning());
+ static::assertFalse(in_array($trx->getId(), $running));
+ }
+
+ public function testCreateAndAbortTransactionById()
+ {
+ $trx = $this->transactionHandler->create();
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue(is_string($trx->getId()));
+
+ static::assertTrue($this->transactionHandler->abort($trx->getId()));
+ $status = $this->transactionHandler->getStatus($trx);
+
+ static::assertEquals($trx->getId(), $status['id']);
+ static::assertEquals('aborted', $status['status']);
+
+ $running = array_map(function($trx) { return $trx['id']; }, $this->transactionHandler->getRunning());
+ static::assertFalse(in_array($trx->getId(), $running));
+ }
+
+ public function testCreateAndCommitTransaction()
+ {
+ $trx = $this->transactionHandler->create();
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue(is_string($trx->getId()));
+
+ static::assertTrue($this->transactionHandler->commit($trx));
+ $status = $this->transactionHandler->getStatus($trx);
+
+ static::assertEquals($trx->getId(), $status['id']);
+ static::assertEquals('committed', $status['status']);
+
+ $running = array_map(function($trx) { return $trx['id']; }, $this->transactionHandler->getRunning());
+ static::assertFalse(in_array($trx->getId(), $running));
+ }
+
+ public function testCreateAndCommitTransactionById()
+ {
+ $trx = $this->transactionHandler->create();
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue(is_string($trx->getId()));
+
+ static::assertTrue($this->transactionHandler->commit($trx->getId()));
+ $status = $this->transactionHandler->getStatus($trx);
+
+ static::assertEquals($trx->getId(), $status['id']);
+ static::assertEquals('committed', $status['status']);
+
+ $running = array_map(function($trx) { return $trx['id']; }, $this->transactionHandler->getRunning());
+ static::assertFalse(in_array($trx->getId(), $running));
+ }
+
+ public function testCreateAndGetStatusTransaction()
+ {
+ $trx = $this->transactionHandler->create();
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue(is_string($trx->getId()));
+
+ $status = $this->transactionHandler->getStatus($trx);
+
+ static::assertEquals($trx->getId(), $status['id']);
+ static::assertEquals('running', $status['status']);
+
+ $running = array_map(function($trx) { return $trx['id']; }, $this->transactionHandler->getRunning());
+ static::assertTrue(in_array($trx->getId(), $running));
+ }
+
+ public function testCreateAndGetStatusTransactionById()
+ {
+ $trx = $this->transactionHandler->create();
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue(is_string($trx->getId()));
+
+ $status = $this->transactionHandler->getStatus($trx->getId());
+
+ static::assertEquals($trx->getId(), $status['id']);
+ static::assertEquals('running', $status['status']);
+
+ $running = array_map(function($trx) { return $trx['id']; }, $this->transactionHandler->getRunning());
+ static::assertTrue(in_array($trx->getId(), $running));
+ }
+
+ public function testGetStatusForNonExistingTransaction()
+ {
+ $found = false;
+ try {
+ $this->transactionHandler->getStatus("999999999999");
+ $found = true;
+ } catch (\Exception $e) {
+ static::assertEquals(404, $e->getCode());
+ }
+ static::assertFalse($found);
+ }
+
+ public function testCreateWithCollections()
+ {
+ $trx = new StreamingTransaction($this->connection, [
+ TransactionBase::ENTRY_COLLECTIONS => [
+ TransactionBase::ENTRY_READ => [ $this->collection1->getName(), $this->collection2->getName() ]
+ ]
+ ]);
+
+ $trx = $this->transactionHandler->create($trx);
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue(is_string($trx->getId()));
+
+ $collection1 = $trx->getCollection($this->collection1->getName());
+ static::assertEquals($this->collection1->getName(), $collection1->getName());
+ static::assertEquals('read', $collection1->getMode());
+
+ $collection2 = $trx->getCollection($this->collection2->getName());
+ static::assertEquals($this->collection2->getName(), $collection2->getName());
+ static::assertEquals('read', $collection2->getMode());
+
+ $status = $this->transactionHandler->getStatus($trx->getId());
+
+ static::assertEquals($trx->getId(), $status['id']);
+ static::assertEquals('running', $status['status']);
+
+ $running = array_map(function($trx) { return $trx['id']; }, $this->transactionHandler->getRunning());
+ static::assertTrue(in_array($trx->getId(), $running));
+ }
+
+ public function testCreateWithCollectionsAndModes()
+ {
+ $trx = new StreamingTransaction($this->connection, [
+ TransactionBase::ENTRY_COLLECTIONS => [
+ TransactionBase::ENTRY_WRITE => [ $this->collection1->getName() ],
+ TransactionBase::ENTRY_EXCLUSIVE => [ $this->collection2->getName() ]
+ ]
+ ]);
+
+ $trx = $this->transactionHandler->create($trx);
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue(is_string($trx->getId()));
+
+ $collection1 = $trx->getCollection($this->collection1->getName());
+ static::assertEquals($this->collection1->getName(), $collection1->getName());
+ static::assertEquals('write', $collection1->getMode());
+
+ $collection2 = $trx->getCollection($this->collection2->getName());
+ static::assertEquals($this->collection2->getName(), $collection2->getName());
+ static::assertEquals('exclusive', $collection2->getMode());
+
+ $status = $this->transactionHandler->getStatus($trx->getId());
+
+ static::assertEquals($trx->getId(), $status['id']);
+ static::assertEquals('running', $status['status']);
+
+ $running = array_map(function($trx) { return $trx['id']; }, $this->transactionHandler->getRunning());
+ static::assertTrue(in_array($trx->getId(), $running));
+ }
+
+ public function testGetCollection()
+ {
+ $trx = new StreamingTransaction($this->connection, [
+ TransactionBase::ENTRY_COLLECTIONS => [
+ TransactionBase::ENTRY_READ => [ $this->collection1->getName(), $this->collection2->getName() ]
+ ]
+ ]);
+
+ $trx = $this->transactionHandler->create($trx);
+ $this->_shutdown[] = $trx;
+
+ $collection1 = $trx->getCollection($this->collection1->getName());
+ static::assertEquals($this->collection1->getName(), $collection1->getName());
+ static::assertEquals('read', $collection1->getMode());
+
+ $collection2 = $trx->getCollection($this->collection2->getName());
+ static::assertEquals($this->collection2->getName(), $collection2->getName());
+ static::assertEquals('read', $collection2->getMode());
+
+ $found = false;
+ try {
+ $trx->getCollection('piff');
+ $found = true;
+ } catch (\Exception $e) {
+ }
+ static::assertFalse($found);
+ }
+
+ public function testInsert()
+ {
+ $trx = new StreamingTransaction($this->connection, [
+ TransactionBase::ENTRY_COLLECTIONS => [
+ TransactionBase::ENTRY_WRITE => [ $this->collection1->getName() ]
+ ]
+ ]);
+
+ $trx = $this->transactionHandler->create($trx);
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue(is_string($trx->getId()));
+
+ $trxCollection = $trx->getCollection($this->collection1->getName());
+ static::assertEquals($this->collection1->getName(), $trxCollection->getName());
+
+ $documentHandler = new DocumentHandler($this->connection);
+ $result = $documentHandler->save($trxCollection, [ '_key' => 'test', 'value' => 'test' ]);
+ static::assertEquals('test', $result);
+
+ // non-transactional lookup should not find the document
+ $found = false;
+ try {
+ $documentHandler->getById($this->collection1->getName(), "test");
+ $found = true;
+ } catch (\Exception $e) {
+ static::assertEquals(404, $e->getCode());
+ }
+ static::assertFalse($found);
+
+ // transactional lookup should find the document
+ $doc = $documentHandler->getById($trxCollection, "test");
+ static::assertEquals('test', $doc->getKey());
+
+ // now commit
+ static::assertTrue($this->transactionHandler->commit($trx->getId()));
+
+ // non-transactional lookup should find the document now too
+ $doc = $documentHandler->getById($this->collection1->getName(), "test");
+ static::assertEquals('test', $doc->getKey());
+ }
+
+ public function testRemove()
+ {
+ // insert a document before the transaction
+ $documentHandler = new DocumentHandler($this->connection);
+ $result = $documentHandler->save($this->collection1->getName(), [ '_key' => 'test', 'value' => 'test' ]);
+ static::assertEquals('test', $result);
+
+ $trx = new StreamingTransaction($this->connection, [
+ TransactionBase::ENTRY_COLLECTIONS => [
+ TransactionBase::ENTRY_WRITE => [ $this->collection1->getName() ]
+ ]
+ ]);
+
+ $trx = $this->transactionHandler->create($trx);
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue(is_string($trx->getId()));
+
+ $trxCollection = $trx->getCollection($this->collection1->getName());
+ static::assertEquals($this->collection1->getName(), $trxCollection->getName());
+
+ // document should be present inside transaction
+ $doc = $documentHandler->getById($trxCollection, "test");
+ static::assertEquals('test', $doc->getKey());
+
+ // remove document inside transaction
+ $result = $documentHandler->removeById($trxCollection, 'test');
+ static::assertTrue($result);
+
+ // transactional lookup should not find the document
+ $found = false;
+ try {
+ $documentHandler->getById($trxCollection, "test");
+ $found = true;
+ } catch (\Exception $e) {
+ static::assertEquals(404, $e->getCode());
+ }
+ static::assertFalse($found);
+
+ // non-transactional lookup should still see it
+ $doc = $documentHandler->getById($this->collection1->getName(), "test");
+ static::assertEquals('test', $doc->getKey());
+
+ // now commit
+ static::assertTrue($this->transactionHandler->commit($trx->getId()));
+
+ // now it should be gone
+ $found = false;
+ try {
+ $documentHandler->getById($this->collection1->getName(), "test");
+ $found = true;
+ } catch (\Exception $e) {
+ static::assertEquals(404, $e->getCode());
+ }
+ static::assertFalse($found);
+ }
+
+ public function testUpdate()
+ {
+ // insert a document before the transaction
+ $documentHandler = new DocumentHandler($this->connection);
+ $result = $documentHandler->save($this->collection1->getName(), [ '_key' => 'test', 'value' => 'test' ]);
+ static::assertEquals('test', $result);
+
+ $trx = new StreamingTransaction($this->connection, [
+ TransactionBase::ENTRY_COLLECTIONS => [
+ TransactionBase::ENTRY_WRITE => [ $this->collection1->getName() ]
+ ]
+ ]);
+
+ $trx = $this->transactionHandler->create($trx);
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue(is_string($trx->getId()));
+
+ $trxCollection = $trx->getCollection($this->collection1->getName());
+ static::assertEquals($this->collection1->getName(), $trxCollection->getName());
+
+ // document should be present inside transaction
+ $doc = $documentHandler->getById($trxCollection, "test");
+ static::assertEquals('test', $doc->getKey());
+ static::assertEquals('test', $doc->value);
+
+ // update document inside transaction
+ $doc->value = 'foobar';
+ $result = $documentHandler->updateById($trxCollection, 'test', $doc);
+ static::assertTrue($result);
+
+ // transactional lookup should find the modified document
+ $doc = $documentHandler->getById($trxCollection, "test");
+ static::assertEquals('test', $doc->getKey());
+ static::assertEquals('foobar', $doc->value);
+
+ // non-transactional lookup should still see the old document
+ $doc = $documentHandler->getById($this->collection1->getName(), "test");
+ static::assertEquals('test', $doc->getKey());
+ static::assertEquals('test', $doc->value);
+
+ // now commit
+ static::assertTrue($this->transactionHandler->commit($trx->getId()));
+
+ $doc = $documentHandler->getById($this->collection1->getName(), "test");
+ static::assertEquals('test', $doc->getKey());
+ static::assertEquals('foobar', $doc->value);
+ }
+
+ public function testReplace()
+ {
+ // insert a document before the transaction
+ $documentHandler = new DocumentHandler($this->connection);
+ $result = $documentHandler->save($this->collection1->getName(), [ '_key' => 'test', 'value' => 'test' ]);
+ static::assertEquals('test', $result);
+
+ $trx = new StreamingTransaction($this->connection, [
+ TransactionBase::ENTRY_COLLECTIONS => [
+ TransactionBase::ENTRY_WRITE => [ $this->collection1->getName() ]
+ ]
+ ]);
+
+ $trx = $this->transactionHandler->create($trx);
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue(is_string($trx->getId()));
+
+ $trxCollection = $trx->getCollection($this->collection1->getName());
+ static::assertEquals($this->collection1->getName(), $trxCollection->getName());
+
+ // document should be present inside transaction
+ $doc = $documentHandler->getById($trxCollection, "test");
+ static::assertEquals('test', $doc->getKey());
+ static::assertEquals('test', $doc->value);
+
+ // replace document inside transaction
+ unset($doc->value);
+ $doc->hihi = 'hoho';
+ $result = $documentHandler->replaceById($trxCollection, 'test', $doc);
+ static::assertTrue($result);
+
+ // transactional lookup should find the modified document
+ $doc = $documentHandler->getById($trxCollection, "test");
+ static::assertEquals('test', $doc->getKey());
+ static::assertEquals('hoho', $doc->hihi);
+ static::assertObjectNotHasAttribute('value', $doc);
+
+ // non-transactional lookup should still see the old document
+ $doc = $documentHandler->getById($this->collection1->getName(), "test");
+ static::assertEquals('test', $doc->getKey());
+ static::assertEquals('test', $doc->value);
+ static::assertObjectNotHasAttribute('hihi', $doc);
+
+ // now commit
+ static::assertTrue($this->transactionHandler->commit($trx->getId()));
+
+ $doc = $documentHandler->getById($this->collection1->getName(), "test");
+ static::assertEquals('test', $doc->getKey());
+ static::assertEquals('hoho', $doc->hihi);
+ static::assertObjectNotHasAttribute('value', $doc);
+ }
+
+ public function testTruncate()
+ {
+ $stmt = new Statement($this->connection, [
+ 'query' => 'FOR i IN 1..10 INSERT { _key: CONCAT("test", i), value: i } INTO @@collection',
+ 'bindVars' => [ '@collection' => $this->collection1->getName() ]
+ ]);
+ $stmt->execute();
+
+ $trx = new StreamingTransaction($this->connection, [
+ TransactionBase::ENTRY_COLLECTIONS => [
+ TransactionBase::ENTRY_WRITE => [ $this->collection1->getName() ]
+ ]
+ ]);
+
+ $trx = $this->transactionHandler->create($trx);
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue(is_string($trx->getId()));
+
+ $trxCollection = $trx->getCollection($this->collection1->getName());
+ static::assertEquals($this->collection1->getName(), $trxCollection->getName());
+
+ // truncate the collection inside the transaction
+ $this->collectionHandler->truncate($trxCollection);
+
+ // transactional lookup should not find any documents
+ $collectionHandler = new CollectionHandler($this->connection);
+ static::assertEquals(0, $collectionHandler->count($trxCollection));
+ $documentHandler = new DocumentHandler($this->connection);
+ $found = false;
+ for ($i = 1; $i <= 10; ++$i) {
+ try {
+ $documentHandler->getById($trxCollection, "test" . $i);
+ $found = true;
+ break;
+ } catch (\Exception $e) {
+ static::assertEquals(404, $e->getCode());
+ }
+ }
+ static::assertFalse($found);
+
+ // non-transactional lookup should find them all!
+ static::assertEquals(10, $this->collectionHandler->count($this->collection1->getName()));
+ for ($i = 1; $i <= 10; ++$i) {
+ $doc = $documentHandler->getById($this->collection1->getName(), "test" . $i);
+ self::assertEquals('test' . $i, $doc->getKey());
+ }
+ }
+
+ public function testQuery()
+ {
+ $trx = new StreamingTransaction($this->connection, [
+ TransactionBase::ENTRY_COLLECTIONS => [
+ TransactionBase::ENTRY_WRITE => [ $this->collection1->getName() ]
+ ]
+ ]);
+
+ $trx = $this->transactionHandler->create($trx);
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue(is_string($trx->getId()));
+
+ $trxCollection = $trx->getCollection($this->collection1->getName());
+ static::assertEquals($this->collection1->getName(), $trxCollection->getName());
+
+ // execute query in transaction
+ $result = $trx->query([
+ 'query' => 'FOR i IN 1..10 INSERT { _key: CONCAT("test", i), value: i } INTO @@collection',
+ 'bindVars' => [ '@collection' => $this->collection1->getName() ]
+ ]);
+
+ // non-transactional lookup should not find any documents
+ $documentHandler = new DocumentHandler($this->connection);
+ $found = false;
+ for ($i = 1; $i <= 10; ++$i) {
+ try {
+ $documentHandler->getById($this->collection1->getName(), "test" . $i);
+ $found = true;
+ break;
+ } catch (\Exception $e) {
+ static::assertEquals(404, $e->getCode());
+ }
+ }
+ static::assertFalse($found);
+
+ // documents should not be visible outside of transaction
+ $collectionHandler = new CollectionHandler($this->connection);
+ static::assertEquals(0, $this->collectionHandler->count($this->collection1->getName()));
+
+ // documents should be visible outside of transaction
+ static::assertEquals(10, $collectionHandler->count($trxCollection));
+ }
+
+ public function testCommitAndthenCommitTransaction()
+ {
+ $trx = $this->transactionHandler->create();
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue($this->transactionHandler->commit($trx));
+ $status = $this->transactionHandler->getStatus($trx);
+
+ static::assertTrue($this->transactionHandler->commit($trx));
+ }
+
+ public function testCommitAndthenAbortTransaction()
+ {
+ $trx = $this->transactionHandler->create();
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue($this->transactionHandler->commit($trx));
+ $status = $this->transactionHandler->getStatus($trx);
+
+ $success = false;
+ try {
+ $this->transactionHandler->abort($trx);
+ $success = true;
+ } catch (\Exception $e) {
+ }
+
+ static::assertFalse($success);
+ }
+
+ public function testAbortAndthenAbortTransaction()
+ {
+ $trx = $this->transactionHandler->create();
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue($this->transactionHandler->abort($trx));
+ $status = $this->transactionHandler->getStatus($trx);
+
+ static::assertTrue($this->transactionHandler->abort($trx));
+ }
+
+ public function testAbortAndthenCommitTransaction()
+ {
+ $trx = $this->transactionHandler->create();
+ $this->_shutdown[] = $trx;
+ static::assertInstanceOf(StreamingTransaction::class, $trx);
+
+ static::assertTrue($this->transactionHandler->abort($trx));
+ $status = $this->transactionHandler->getStatus($trx);
+
+ $success = false;
+ try {
+ $this->transactionHandler->commit($trx);
+ $success = true;
+ } catch (\Exception $e) {
+ }
+
+ static::assertFalse($success);
+ }
+
+ public function tearDown()
+ {
+ foreach ($this->_shutdown as $trx) {
+ try {
+ $this->transactionHandler->abort($trx);
+ } catch (\Exception $e) {
+ }
+ }
+
+ try {
+ $this->collectionHandler->drop('ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp);
+ } catch (\Exception $e) {
+ // don't bother us, if it's already deleted.
+ }
+ try {
+ $this->collectionHandler->drop('ArangoDB_PHP_TestSuite_TestCollection_02' . '_' . static::$testsTimestamp);
+ } catch (\Exception $e) {
+ // don't bother us, if it's already deleted.
+ }
+ }
+}
diff --git a/tests/TransactionTest.php b/tests/TransactionTest.php
index db66c624..5418cfba 100644
--- a/tests/TransactionTest.php
+++ b/tests/TransactionTest.php
@@ -275,10 +275,10 @@ function () {
// check if getters work fine
static::assertEquals(
- $writeCollections, $transaction->writeCollections, 'Did not return writeCollections, instead returned: ' . print_r($transaction->writeCollections, 1)
+ [$writeCollections], $transaction->writeCollections, 'Did not return writeCollections, instead returned: ' . print_r($transaction->writeCollections, 1)
);
static::assertEquals(
- $readCollections, $transaction->readCollections, 'Did not return readCollections, instead returned: ' . print_r($transaction->readCollections, 1)
+ [$readCollections], $transaction->readCollections, 'Did not return readCollections, instead returned: ' . print_r($transaction->readCollections, 1)
);
static::assertEquals(
$action, $transaction->action, 'Did not return action, instead returned: ' . $transaction->action
@@ -342,6 +342,34 @@ function () {
$result = $transaction->execute();
static::assertTrue($result, 'Did not return true, instead returned: ' . $result);
}
+
+
+ /**
+ * Test if we can create and execute a transaction by using getters/setters
+ */
+ public function testCreateAndExecuteTransactionExclusiveWithGettersSetters()
+ {
+ $exclusiveCollections = [$this->collection1->getName(), $this->collection2->getName()];
+ $action = '
+ function () {
+ var db = require("internal").db;
+ db.' . $this->collection1->getName() . '.save({ test : "hello" });
+ }';
+
+ $transaction = new Transaction($this->connection);
+
+ // check if setters work fine
+ $transaction->setExclusiveCollections($exclusiveCollections);
+ $transaction->setAction($action);
+
+ // check if getters work fine
+
+ static::assertEquals(
+ $exclusiveCollections, $transaction->getExclusiveCollections());
+
+ $result = $transaction->execute();
+ static::assertTrue($result, 'Did not return true, instead returned: ' . $result);
+ }
/**
From 35777d26292724dcb645f528d7d18efb6744fecc Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Mon, 5 Aug 2019 12:51:03 +0200
Subject: [PATCH 016/101] adjust Docker container
---
CHANGELOG.md | 3 +++
.../StreamingTransactionHandler.php | 21 +++++++++++++++----
tests/travis/setup_arangodb.sh | 4 ++--
3 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2c7062fc..8e298c14 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -54,6 +54,9 @@ via an instance variable in the `StreamingTransactionHandler`.
Streaming transactions are automatically aborted on shutdown via a shutdown function, and all
transactions started via `StreamingTransactionHandler` instances that were neither committed nor
aborted by the user will be aborted.
+In order to take over the management of a transaction from the `StreamingTransactionHandler`, it is
+possible to call the handler's `stealTransaction()` method with the transaction's id. This will
+make the handler "forget" about auto-aborting this particular transaction.
The `CollectionHandler` class got a new method `createTtlIndex` for creating time-to-live (TTL)
diff --git a/lib/ArangoDBClient/StreamingTransactionHandler.php b/lib/ArangoDBClient/StreamingTransactionHandler.php
index 50d431e1..c7d83a20 100644
--- a/lib/ArangoDBClient/StreamingTransactionHandler.php
+++ b/lib/ArangoDBClient/StreamingTransactionHandler.php
@@ -76,7 +76,20 @@ public function closePendingTransactions()
}
$this->_pendingTransactions = [];
}
-
+
+
+ /**
+ * Steal the transaction from the handler, so that it is not responsible anymore
+ * for auto-aborting it on shutdown
+ *
+ * @param string $id - transaction id
+ */
+ public function stealTransaction($id)
+ {
+ unset($this->_pendingTransactions[$id]);
+ }
+
+
/**
* Retrieves the status of a transaction
*
@@ -99,7 +112,7 @@ public function getStatus($trx)
$status = $jsonResponse['result']['status'];
if ($status === 'aborted' || $status === 'committed') {
- unset($this->_pendingTransactions[$id]);
+ $this->stealTransaction($id);
}
return $jsonResponse['result'];
@@ -122,7 +135,7 @@ public function commit($trx)
$id = (string) $trx;
}
- unset($this->_pendingTransactions[$id]);
+ $this->stealTransaction($id);
$this->getConnection()->put(UrlHelper::buildUrl(Urls::URL_TRANSACTION, [$id]), '');
return true;
@@ -145,7 +158,7 @@ public function abort($trx)
$id = (string) $trx;
}
- unset($this->_pendingTransactions[$id]);
+ $this->stealTransaction($id);
$this->getConnection()->delete(UrlHelper::buildUrl(Urls::URL_TRANSACTION, [$id]));
return true;
diff --git a/tests/travis/setup_arangodb.sh b/tests/travis/setup_arangodb.sh
index ddab1448..8193ec06 100644
--- a/tests/travis/setup_arangodb.sh
+++ b/tests/travis/setup_arangodb.sh
@@ -35,8 +35,8 @@ echo "./phpunit --version"
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $DIR
-docker pull arangodb/arangodb-preview:3.5.0-rc.4
-docker run -d -e ARANGO_ROOT_PASSWORD="test" -p 8529:8529 arangodb/arangodb-preview:3.5.0-rc.4
+docker pull arangodb/arangodb-preview:3.5.0-rc.7
+docker run -d -e ARANGO_ROOT_PASSWORD="test" -p 8529:8529 arangodb/arangodb-preview:3.5.0-rc.7
sleep 2
From 47f7d68f465bb3018d96d3a77f7923c22884a420 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Mon, 5 Aug 2019 16:21:31 +0200
Subject: [PATCH 017/101] implemented several methods, added deprecation
warnings
---
CHANGELOG.md | 53 +++-
lib/ArangoDBClient/CollectionHandler.php | 374 +++++++++++++++--------
tests/CollectionBasicTest.php | 198 ++++++++++++
3 files changed, 488 insertions(+), 137 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e298c14..320392ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,8 @@ Streaming transactions currently support the following operations:
- truncating a collection, i.e. `CollectionHandler::truncate()`
- running AQL queries, i.e. `Statement::execute()`
+Other driver operations than the above are currently not supported within streaming transactions.
+
Streaming transactions are provided by a new class `StreamingTransaction` and a new handler
`StreamingTransactionHandler`.
@@ -59,13 +61,58 @@ possible to call the handler's `stealTransaction()` method with the transaction'
make the handler "forget" about auto-aborting this particular transaction.
+Deprecated several methods in `CollectionHandler`, because they are deprecated in the arangod
+server as well:
+
+- CollectionHandler::fulltext()
+- CollectionHandler::updateByExample()
+- CollectionHandler::replaceByExample()
+- CollectionHandler::range()
+- CollectionHandler::near()
+- CollectionHandler::within()
+
+
+Added method `CollectionHandler::getShards()` to retrieve the list of available shards of a collection.
+
+Added method `CollectionHandler::getResponsibleShard()` to retrieve the shard id of the shard
+responsible for storing a particular document.
+
+
+All index-specific index-creation methods in `CollectionHandler` are now deprecated in favor of
+the much more general method `CollectionHandler::createIndex()`. This new methods replaces the
+following deprecated methods:
+
+- CollectionHandler::createHashIndex()
+- CollectionHandler::createFulltextIndex()
+- CollectionHandler::createSkipListIndex()
+- CollectionHandler::createPersistentIndex()
+- CollectionHandler::createTtlIndex()
+- CollectionHandler::createGeoIndex()
+- CollectionHandler::index()
+
+`CollectionHandler::createIndex()` now also supports named indexes and background indexing via
+setting the respective options on index creation, e.g.
+
+ $collectionHandler->createIndex($collection, [
+ 'type' => 'persistent',
+ 'name' => 'my-index',
+ 'fields' => ['a', 'b'],
+ 'unique' => true,
+ 'sparse' => false,
+ 'inBackground' => true
+ ]);
+
+The now deprecated specialized index methods will be removed in a future release of the driver
+in favor of the generic `createIndex` method.
+
+
The `CollectionHandler` class got a new method `createTtlIndex` for creating time-to-live (TTL)
indexes on the server.
-All methods for index creation also got an extra optional attribute `$inBackground` that enables
-background index creation.
+All specialized methods for index creation also got an extra optional attribute `$inBackground` that
+enables background index creation.
-Added support for the following attributes on collection level:
+Added driver support for the following attributes on collection level:
- distributeShardsLike
- smartJoinAttribute (only effective in ArangoDB enterprise edition)
diff --git a/lib/ArangoDBClient/CollectionHandler.php b/lib/ArangoDBClient/CollectionHandler.php
index 737d1ae4..6f653fe9 100644
--- a/lib/ArangoDBClient/CollectionHandler.php
+++ b/lib/ArangoDBClient/CollectionHandler.php
@@ -57,6 +57,11 @@ class CollectionHandler extends Handler
*/
const OPTION_KEYS = 'keys';
+ /**
+ * stream parameter
+ */
+ const OPTION_STREAM = 'stream';
+
/**
* left parameter
*/
@@ -201,6 +206,16 @@ class CollectionHandler extends Handler
* revision option
*/
const OPTION_REVISION = 'revision';
+
+ /**
+ * responsible shard option
+ */
+ const OPTION_RESPONSIBLE_SHARD = 'responsibleShard';
+
+ /**
+ * shards option
+ */
+ const OPTION_SHARDS = 'shards';
/**
* properties option
@@ -485,7 +500,7 @@ public function figures($collection)
*
* @throws Exception
*
- * @param mixed $collectionId - collection id as a string or number
+ * @param mixed $collection - collection as string or object
* @param boolean $withRevisions - optional boolean whether or not to include document revision ids
* in the checksum calculation.
* @param boolean $withData - optional boolean whether or not to include document body data in the
@@ -493,10 +508,9 @@ public function figures($collection)
*
* @return array - array containing keys "checksum" and "revision"
*/
- public function getChecksum($collectionId, $withRevisions = false, $withData = false)
+ public function getChecksum($collection, $withRevisions = false, $withData = false)
{
-
- $url = UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collectionId, self::OPTION_CHECKSUM]);
+ $url = UrlHelper::buildUrl(Urls::URL_COLLECTION, [$this->makeCollection($collection), self::OPTION_CHECKSUM]);
$url = UrlHelper::appendParamsUrl($url, ['withRevisions' => $withRevisions, 'withData' => $withData]);
$response = $this->getConnection()->get($url);
@@ -511,14 +525,13 @@ public function getChecksum($collectionId, $withRevisions = false, $withData = f
*
* @throws Exception
*
- * @param mixed $collectionId - collection id as a string or number
+ * @param mixed $collection - collection as string or object
*
* @return array - containing a key revision
*/
- public function getRevision($collectionId)
+ public function getRevision($collection)
{
-
- $url = UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collectionId, self::OPTION_REVISION]);
+ $url = UrlHelper::buildUrl(Urls::URL_COLLECTION, [$this->makeCollection($collection), self::OPTION_REVISION]);
$response = $this->getConnection()->get($url);
return $response->getJson();
@@ -529,22 +542,16 @@ public function getRevision($collectionId)
*
* @throws Exception
*
- * @param mixed $collection - collection id as string or number or collection object
+ * @param mixed $collection - collection as string or object
* @param string $name - new name for collection
*
* @return bool - always true, will throw if there is an error
*/
public function rename($collection, $name)
{
- $collectionId = $this->getCollectionId($collection);
-
- if ($this->isValidCollectionId($collectionId)) {
- throw new ClientException('Cannot alter a collection without a collection id');
- }
-
$params = [Collection::ENTRY_NAME => $name];
$this->getConnection()->put(
- UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collectionId, self::OPTION_RENAME]),
+ UrlHelper::buildUrl(Urls::URL_COLLECTION, [$this->makeCollection($collection), self::OPTION_RENAME]),
$this->json_encode_wrapper($params)
);
@@ -558,20 +565,14 @@ public function rename($collection, $name)
*
* @throws Exception
*
- * @param mixed $collection - collection id as string or number or collection object
+ * @param mixed $collection - collection as string or object
*
* @return HttpResponse - HTTP response object
*/
public function load($collection)
{
- $collectionId = $this->getCollectionId($collection);
-
- if ($this->isValidCollectionId($collectionId)) {
- throw new ClientException('Cannot alter a collection without a collection id');
- }
-
$result = $this->getConnection()->put(
- UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collectionId, self::OPTION_LOAD]),
+ UrlHelper::buildUrl(Urls::URL_COLLECTION, [$this->makeCollection($collection), self::OPTION_LOAD]),
''
);
@@ -585,20 +586,14 @@ public function load($collection)
*
* @throws Exception
*
- * @param mixed $collection - collection id as string or number or collection object
+ * @param mixed $collection - collection as string or object
*
* @return HttpResponse - HTTP response object
*/
public function unload($collection)
{
- $collectionId = $this->getCollectionId($collection);
-
- if ($this->isValidCollectionId($collectionId)) {
- throw new ClientException('Cannot alter a collection without a collection id');
- }
-
$result = $this->getConnection()->put(
- UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collectionId, self::OPTION_UNLOAD]),
+ UrlHelper::buildUrl(Urls::URL_COLLECTION, [$this->makeCollection($collection), self::OPTION_UNLOAD]),
''
);
@@ -650,18 +645,12 @@ public function truncate($collection)
*/
public function drop($collection, array $options = [])
{
- $collectionName = $this->getCollectionName($collection);
-
- if ($this->isValidCollectionId($collectionName)) {
- throw new ClientException('Cannot alter a collection without a collection id');
- }
-
$appendix = '';
if (is_array($options) && isset($options['isSystem'])) {
$appendix = '?isSystem=' . UrlHelper::getBoolString($options['isSystem']);
}
- $this->getConnection()->delete(UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collectionName]) . $appendix);
+ $this->getConnection()->delete(UrlHelper::buildUrl(Urls::URL_COLLECTION, [$this->makeCollection($collection)]) . $appendix);
return true;
}
@@ -766,7 +755,7 @@ public function getCollectionName($collection)
*
* @throws Exception
*
- * @param mixed $collectionId - collection id as string or number
+ * @param mixed $collectionId - collection as string or object
* @param mixed $importFileName - The filename that holds the import data.
* @param array $options - optional - an array of options.
*
Options are :
@@ -784,11 +773,7 @@ public function getCollectionName($collection)
*
* @return array - returns an array with the server's response data from the import command
*/
- public function importFromFile(
- $collectionId,
- $importFileName,
- array $options = []
- )
+ public function importFromFile($collection, $importFileName, array $options = [])
{
$contents = file_get_contents($importFileName);
@@ -796,7 +781,7 @@ public function importFromFile(
throw new ClientException('Input file "' . $importFileName . '" could not be found.');
}
- return $this->import($collectionId, $contents, $options);
+ return $this->import($collection, $contents, $options);
}
@@ -806,7 +791,7 @@ public function importFromFile(
* This will throw on all errors except insertion errors
*
*
- * @param $collection mixed $collection - collection id as string or number
+ * @param $collection mixed $collection - collection as string or object
* @param string|array $importData - The data to import. This can be a string holding the data according to the type of import, or an array of documents
* @param array $options - optional - an array of options.
*
Options are :
@@ -832,11 +817,7 @@ public function importFromFile(
* @throws \ArangoDBClient\Exception
* @throws \ArangoDBClient\ClientException
*/
- public function import(
- $collection,
- $importData,
- array $options = []
- )
+ public function import($collection, $importData, array $options = [])
{
$collection = $this->makeCollection($collection);
@@ -880,20 +861,23 @@ public function import(
/**
* Create a hash index
*
- * @param string $collectionId - the collection id
+ * @param mixed $collection - the collection as name or object
* @param array $fields - an array of fields
* @param bool $unique - whether the values in the index should be unique or not
* @param bool $sparse - whether the index should be sparse
* @param bool $inBackground - true if index shall be created in background
*
- * @link https://www.arangodb.com/docs/devel/indexing-hash.html
+ * @deprecated use CollectionHandler::createIndex instead
*
* @return array - server response of the created index
* @throws \ArangoDBClient\Exception
*/
- public function createHashIndex($collectionId, array $fields, $unique = null, $sparse = null, $inBackground = false)
+ public function createHashIndex($collection, array $fields, $unique = null, $sparse = null, $inBackground = false)
{
- $indexOptions = [];
+ $indexOptions = [
+ self::OPTION_TYPE => 'hash',
+ self::OPTION_FIELDS => $fields
+ ];
if ($unique) {
$indexOptions[self::OPTION_UNIQUE] = (bool) $unique;
@@ -905,25 +889,28 @@ public function createHashIndex($collectionId, array $fields, $unique = null, $s
$indexOptions[self::OPTION_IN_BACKGROUND] = (bool) $inBackground;
}
- return $this->index($collectionId, self::OPTION_HASH_INDEX, $fields, null, $indexOptions);
+ return $this->createIndex($collection, $indexOptions);
}
/**
* Create a fulltext index
*
- * @param string $collectionId - the collection id
+ * @param mixed $collection - collection as string or object
* @param array $fields - an array of fields
* @param int $minLength - the minimum length of words to index
* @param bool $inBackground - true if index shall be created in background
*
- * @link https://www.arangodb.com/docs/devel/indexing-fulltext.html
+ * @deprecated use CollectionHandler::createIndex instead
*
* @return array - server response of the created index
* @throws \ArangoDBClient\Exception
*/
- public function createFulltextIndex($collectionId, array $fields, $minLength = null, $inBackground = false)
+ public function createFulltextIndex($collection, array $fields, $minLength = null, $inBackground = false)
{
- $indexOptions = [];
+ $indexOptions = [
+ self::OPTION_TYPE => 'fulltext',
+ self::OPTION_FIELDS => $fields
+ ];
if ($minLength) {
$indexOptions[self::OPTION_MIN_LENGTH] = $minLength;
@@ -932,26 +919,29 @@ public function createFulltextIndex($collectionId, array $fields, $minLength = n
$indexOptions[self::OPTION_IN_BACKGROUND] = (bool) $inBackground;
}
- return $this->index($collectionId, self::OPTION_FULLTEXT_INDEX, $fields, null, $indexOptions);
+ return $this->createIndex($collection, $indexOptions);
}
/**
* Create a skip-list index
*
- * @param string $collectionId - the collection id
+ * @param mixed $collection - collection as string or object
* @param array $fields - an array of fields
* @param bool $unique - whether the index is unique or not
* @param bool $sparse - whether the index should be sparse
* @param bool $inBackground - true if index shall be created in background
*
- * @link https://www.arangodb.com/docs/devel/indexing-skiplist.html
+ * @deprecated use CollectionHandler::createIndex instead
*
* @return array - server response of the created index
* @throws \ArangoDBClient\Exception
*/
- public function createSkipListIndex($collectionId, array $fields, $unique = null, $sparse = null, $inBackground = false)
+ public function createSkipListIndex($collection, array $fields, $unique = null, $sparse = null, $inBackground = false)
{
- $indexOptions = [];
+ $indexOptions = [
+ self::OPTION_TYPE => 'skiplist',
+ self::OPTION_FIELDS => $fields
+ ];
if ($unique) {
$indexOptions[self::OPTION_UNIQUE] = (bool) $unique;
@@ -963,26 +953,29 @@ public function createSkipListIndex($collectionId, array $fields, $unique = null
$indexOptions[self::OPTION_IN_BACKGROUND] = (bool) $inBackground;
}
- return $this->index($collectionId, self::OPTION_SKIPLIST_INDEX, $fields, null, $indexOptions);
+ return $this->createIndex($collection, $indexOptions);
}
/**
* Create a persistent index
*
- * @param string $collectionId - the collection id
+ * @param mixed $collection - collection as string or object
* @param array $fields - an array of fields
* @param bool $unique - whether the index is unique or not
* @param bool $sparse - whether the index should be sparse
- * @param bool $inBackground - true if index shall be created in background
+ * @param bool $inBackground - true if index shall be created in background
*
- * @link https://www.arangodb.com/docs/devel/indexing-persistent.html
+ * @deprecated use CollectionHandler::createIndex instead
*
* @return array - server response of the created index
* @throws \ArangoDBClient\Exception
*/
- public function createPersistentIndex($collectionId, array $fields, $unique = null, $sparse = null, $inBackground = false)
+ public function createPersistentIndex($collection, array $fields, $unique = null, $sparse = null, $inBackground = false)
{
- $indexOptions = [];
+ $indexOptions = [
+ self::OPTION_TYPE => 'persistent',
+ self::OPTION_FIELDS => $fields
+ ];
if ($unique) {
$indexOptions[self::OPTION_UNIQUE] = (bool) $unique;
@@ -994,50 +987,56 @@ public function createPersistentIndex($collectionId, array $fields, $unique = nu
$indexOptions[self::OPTION_IN_BACKGROUND] = (bool) $inBackground;
}
- return $this->index($collectionId, self::OPTION_PERSISTENT_INDEX, $fields, null, $indexOptions);
+ return $this->createIndex($collection, $indexOptions);
}
/**
* Create a TTL index
*
- * @param string $collectionId - the collection id
+ * @param mixed $collection - collection as string or object
* @param array $fields - an array of fields (only a single one allowed)
* @param number $expireAfter - number of seconds after index value after which documents expire
- * @param bool $inBackground - true if index shall be created in background
+ * @param bool $inBackground - true if index shall be created in background
*
- * @link https://www.arangodb.com/docs/devel/indexing-ttl.html
+ * @deprecated use CollectionHandler::createIndex instead
*
* @return array - server response of the created index
* @throws \ArangoDBClient\Exception
*/
- public function createTtlIndex($collectionId, array $fields, $expireAfter, $inBackground = false)
+ public function createTtlIndex($collection, array $fields, $expireAfter, $inBackground = false)
{
$indexOptions = [
- self::OPTION_EXPIRE_AFTER => (double) $expireAfter
+ self::OPTION_TYPE => 'ttl',
+ self::OPTION_FIELDS => $fields,
+ self::OPTION_EXPIRE_AFTER => (double) $expireAfter
];
+
if ($inBackground) {
$indexOptions[self::OPTION_IN_BACKGROUND] = (bool) $inBackground;
}
- return $this->index($collectionId, self::OPTION_TTL_INDEX, $fields, null, $indexOptions);
+ return $this->createIndex($collection, $indexOptions);
}
/**
* Create a geo index
*
- * @param string $collectionId - the collection id
+ * @param mixed $collection - collection as string or object
* @param array $fields - an array of fields
* @param bool $geoJson - whether to use geoJson or not
* @param bool $inBackground - true if index shall be created in background
*
- * @link https://www.arangodb.com/docs/devel/indexing-geo.html
+ * @deprecated use CollectionHandler::createIndex instead
*
* @return array - server response of the created index
* @throws \ArangoDBClient\Exception
*/
- public function createGeoIndex($collectionId, array $fields, $geoJson = null, $inBackground = false)
+ public function createGeoIndex($collection, array $fields, $geoJson = null, $inBackground = false)
{
- $indexOptions = [];
+ $indexOptions = [
+ self::OPTION_TYPE => 'geo',
+ self::OPTION_FIELDS => $fields,
+ ];
if ($geoJson) {
$indexOptions[self::OPTION_GEOJSON] = (bool) $geoJson;
@@ -1046,7 +1045,7 @@ public function createGeoIndex($collectionId, array $fields, $geoJson = null, $i
$indexOptions[self::OPTION_IN_BACKGROUND] = (bool) $inBackground;
}
- return $this->index($collectionId, self::OPTION_GEO_INDEX, $fields, null, $indexOptions);
+ return $this->createIndex($collection, $indexOptions);
}
/**
@@ -1058,18 +1057,19 @@ public function createGeoIndex($collectionId, array $fields, $geoJson = null, $i
*
* @throws Exception
*
- * @param mixed $collectionId - The id of the collection where the index is to be created
+ * @param mixed $collection - collection as string or object
* @param string $type - index type: hash, skiplist, geo, ttl, fulltext, or persistent
* @param array $attributes - an array of attributes that can be defined like array('a') or array('a', 'b.c')
* @param bool $unique - true/false to create a unique index
* @param array $indexOptions - an associative array of options for the index like array('geoJson' => true, 'sparse' => false)
*
+ * @deprecated use CollectionHandler::createIndex instead
+ *
* @return array - server response of the created index
*/
- public function index($collectionId, $type, array $attributes = [], $unique = false, array $indexOptions = [])
+ public function index($collection, $type, array $attributes = [], $unique = false, array $indexOptions = [])
{
-
- $urlParams = [self::OPTION_COLLECTION => $collectionId];
+ $urlParams = [self::OPTION_COLLECTION => $this->makeCollection($collection)];
$bodyParams = [
self::OPTION_TYPE => $type,
self::OPTION_FIELDS => $attributes,
@@ -1087,8 +1087,43 @@ public function index($collectionId, $type, array $attributes = [], $unique = fa
$httpCode = $response->getHttpCode();
switch ($httpCode) {
case 404:
- throw new ClientException('Collection-identifier is unknown');
+ throw new ClientException('Collection is unknown');
+ break;
+ case 400:
+ throw new ClientException('cannot create unique index due to documents violating uniqueness');
+ break;
+ }
+ return $response->getJson();
+ }
+
+
+ /**
+ * Creates an index on a collection on the server
+ *
+ * This will create an index on the collection on the server and return its id
+ *
+ * This will throw if the index cannot be created
+ *
+ * @throws Exception
+ *
+ * @param mixed $collection - collection as string or object
+ * @param array $indexOptions - an associative array of options for the index like array('type' => hash, 'fields' => ..., 'sparse' => false)
+ *
+ * @return array - server response of the created index
+ * @since 3.5
+ */
+ public function createIndex($collection, array $indexOptions)
+ {
+ $urlParams = [self::OPTION_COLLECTION => $this->makeCollection($collection)];
+
+ $url = UrlHelper::appendParamsUrl(Urls::URL_INDEX, $urlParams);
+ $response = $this->getConnection()->post($url, $this->json_encode_wrapper($indexOptions));
+
+ $httpCode = $response->getHttpCode();
+ switch ($httpCode) {
+ case 404:
+ throw new ClientException('Collection is unknown');
break;
case 400:
throw new ClientException('cannot create unique index due to documents violating uniqueness');
@@ -1125,13 +1160,13 @@ public function getIndex($collection, $indexId)
*
* @throws Exception
*
- * @param mixed $collectionId - collection id as a string or number
+ * @param mixed $collection - collection as string or object
*
* @return array $data - the indexes result-set from the server
*/
- public function getIndexes($collectionId)
+ public function getIndexes($collection)
{
- $urlParams = [self::OPTION_COLLECTION => $collectionId];
+ $urlParams = [self::OPTION_COLLECTION => $this->makeCollection($collection)];
$url = UrlHelper::appendParamsUrl(Urls::URL_INDEX, $urlParams);
$response = $this->getConnection()->get($url);
@@ -1143,37 +1178,95 @@ public function getIndexes($collectionId)
*
* @throws Exception
*
+ * @param mixed $collection - collection as string or object
* @param mixed $indexHandle - index handle (collection name / index id)
*
* @return bool - always true, will throw if there is an error
*/
- public function dropIndex($indexHandle)
+ public function dropIndex($collection, $indexHandle = null)
{
- $handle = explode('/', $indexHandle);
- $this->getConnection()->delete(UrlHelper::buildUrl(Urls::URL_INDEX, [$handle[0], $handle[1]]));
+ if ($indexHandle === null) {
+ $handle = explode('/', $collection);
+ } else {
+ $handle = [ $this->makeCollection($collection), $indexHandle ];
+ }
+
+ if (count($handle) > 2) {
+ throw new ClientException('Invalid index handle');
+ }
+
+ $this->getConnection()->delete(UrlHelper::buildUrl(Urls::URL_INDEX, $handle));
return true;
}
+
+
+ /**
+ * Get the responsible shard for a document
+ *
+ * @throws Exception
+ *
+ * @param mixed $collection - collection as string or object
+ * @param mixed $document - document
+ *
+ * @return string - shard id
+ * @since 3.5
+ */
+ public function getResponsibleShard($collection, $document)
+ {
+ if (is_array($document)) {
+ $data = $document;
+ } else {
+ $data = $document->getAll(['_includeInternals' => true ]);
+ }
+
+ $collection = $this->makeCollection($collection);
+ $url = UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collection, self::OPTION_RESPONSIBLE_SHARD]);
+ $response = $this->getConnection()->put($url, $this->json_encode_wrapper($data));
+ $data = $response->getJson();
+
+ return $data['shardId'];
+ }
+
+
+ /**
+ * Get the shards of a collection
+ *
+ * @throws Exception
+ *
+ * @param mixed $collection - collection as string or object
+ *
+ * @return array - array with shard ids
+ * @since 3.5
+ */
+ public function getShards($collection)
+ {
+ $collection = $this->makeCollection($collection);
+ $url = UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collection, self::OPTION_SHARDS]);
+ $response = $this->getConnection()->get($url);
+ $data = $response->getJson();
+
+ return $data['shards'];
+ }
/**
* Get a random document from the collection.
*
* This will throw if the document cannot be fetched from the server
*
- *
* @throws Exception
*
- * @param mixed $collectionId - collection id as string or number
+ * @param mixed $collection - collection as string or object
*
* @return Document - the document fetched from the server
* @since 1.2
*/
- public function any($collectionId)
+ public function any($collection)
{
$_documentClass = $this->_documentClass;
$data = [
- self::OPTION_COLLECTION => $collectionId,
+ self::OPTION_COLLECTION => $this->makeCollection($collection),
];
$response = $this->getConnection()->put(Urls::URL_ANY, $this->json_encode_wrapper($data));
@@ -1190,7 +1283,7 @@ public function any($collectionId)
/**
* Returns all documents of a collection
*
- * @param mixed $collectionId - collection id as string or number
+ * @param mixed $collection - collection as string or object
* @param array $options - optional array of options.
*
Options are :
*
'_sanitize' - True to remove _id and _rev attributes from result documents. Defaults to false.
@@ -1211,10 +1304,10 @@ public function any($collectionId)
* @throws \ArangoDBClient\Exception
* @throws \ArangoDBClient\ClientException
*/
- public function all($collectionId, array $options = [])
+ public function all($collection, array $options = [])
{
$body = [
- self::OPTION_COLLECTION => $collectionId,
+ self::OPTION_COLLECTION => $this->makeCollection($collection),
];
$body = $this->includeOptionsInBody(
@@ -1225,6 +1318,8 @@ public function all($collectionId, array $options = [])
self::OPTION_SKIP => null,
]
);
+
+ $body[self::OPTION_STREAM] = true;
$response = $this->getConnection()->put(Urls::URL_ALL, $this->json_encode_wrapper($body));
@@ -1278,7 +1373,7 @@ public function getAllIds($collection)
*
* @throws Exception
*
- * @param mixed $collectionId - collection id as string or number
+ * @param mixed $collection - collection as string or object
* @param mixed $document - the example document as a Document object or an array
* @param array $options - optional, prior to v1.0.0 this was a boolean value for sanitize, since v1.0.0 it's an array of options.
*
Options are :
@@ -1297,7 +1392,7 @@ public function getAllIds($collection)
*
* @return cursor - Returns a cursor containing the result
*/
- public function byExample($collectionId, $document, array $options = [])
+ public function byExample($collection, $document, array $options = [])
{
$_documentClass = $this->_documentClass;
@@ -1310,7 +1405,7 @@ public function byExample($collectionId, $document, array $options = [])
}
$body = [
- self::OPTION_COLLECTION => $collectionId,
+ self::OPTION_COLLECTION => $this->makeCollection($collection),
self::OPTION_EXAMPLE => $document->getAllAsObject(['_ignoreHiddenAttributes' => true])
];
@@ -1347,7 +1442,7 @@ public function byExample($collectionId, $document, array $options = [])
*
* @throws Exception
*
- * @param mixed $collectionId - collection id as string or number
+ * @param mixed $collection - collection as string or object
* @param mixed $document - the example document as a Document object or an array
* @param array $options - optional, an array of options.
*
Options are :
@@ -1364,7 +1459,7 @@ public function byExample($collectionId, $document, array $options = [])
* @return Document - the document fetched from the server
* @since 1.2
*/
- public function firstExample($collectionId, $document, array $options = [])
+ public function firstExample($collection, $document, array $options = [])
{
$_documentClass = $this->_documentClass;
@@ -1377,7 +1472,7 @@ public function firstExample($collectionId, $document, array $options = [])
}
$data = [
- self::OPTION_COLLECTION => $collectionId,
+ self::OPTION_COLLECTION => $this->makeCollection($collection),
self::OPTION_EXAMPLE => $document->getAll(['_ignoreHiddenAttributes' => true])
];
@@ -1423,12 +1518,14 @@ public function firstExample($collectionId, $document, array $options = [])
*
'index' - If given, the identifier of the fulltext-index to use.
*
*
+ * @deprecated use AQL queries instead
+ *
* @return cursor - Returns a cursor containing the result
*/
public function fulltext($collection, $attribute, $query, array $options = [])
{
$body = [
- self::OPTION_COLLECTION => $collection,
+ self::OPTION_COLLECTION => $this->makeCollection($collection),
self::OPTION_ATTRIBUTE => $attribute,
self::OPTION_QUERY => $query,
];
@@ -1465,7 +1562,7 @@ public function fulltext($collection, $attribute, $query, array $options = [])
*
* @throws Exception
*
- * @param mixed $collectionId - collection id as string or number
+ * @param mixed $collection - collection as string or number
* @param mixed $example - the example document as a Document object or an array
* @param mixed $newValue - patch document or array which contains the attributes and values to be updated
* @param mixed $options - optional, array of options (see below) or the boolean value for $policy (for compatibility prior to version 1.1 of this method)
@@ -1475,10 +1572,12 @@ public function fulltext($collection, $attribute, $query, array $options = [])
*
'limit' - can be used set a limit on how many documents to update at most. If limit is specified but is less than the number of documents in the collection, it is undefined which of the documents will be updated.
*
*
+ * @deprecated use AQL queries instead
+ *
* @return bool - always true, will throw if there is an error
* @since 1.2
*/
- public function updateByExample($collectionId, $example, $newValue, array $options = [])
+ public function updateByExample($collection, $example, $newValue, array $options = [])
{
$_documentClass = $this->_documentClass;
@@ -1491,7 +1590,7 @@ public function updateByExample($collectionId, $example, $newValue, array $optio
}
$body = [
- self::OPTION_COLLECTION => $collectionId,
+ self::OPTION_COLLECTION => $this->makeCollection($collection),
self::OPTION_EXAMPLE => $example->getAllAsObject(['_ignoreHiddenAttributes' => true]),
self::OPTION_NEW_VALUE => $newValue->getAllAsObject(['_ignoreHiddenAttributes' => true])
];
@@ -1529,7 +1628,7 @@ public function updateByExample($collectionId, $example, $newValue, array $optio
*
* @throws Exception
*
- * @param mixed $collectionId - collection id as string or number
+ * @param mixed $collection - collection as string or object
* @param mixed $example - the example document as a Document object or an array
* @param mixed $newValue - patch document or array which contains the attributes and values to be replaced
* @param mixed $options - optional, array of options (see below) or the boolean value for $policy (for compatibility prior to version 1.1 of this method)
@@ -1539,10 +1638,12 @@ public function updateByExample($collectionId, $example, $newValue, array $optio
*
'limit' - can be used set a limit on how many documents to replace at most. If limit is specified but is less than the number of documents in the collection, it is undefined which of the documents will be replaced.
*
*
+ * @deprecated use AQL queries instead
+ *
* @return bool - always true, will throw if there is an error
* @since 1.2
*/
- public function replaceByExample($collectionId, $example, $newValue, array $options = [])
+ public function replaceByExample($collection, $example, $newValue, array $options = [])
{
$_documentClass = $this->_documentClass;
@@ -1555,7 +1656,7 @@ public function replaceByExample($collectionId, $example, $newValue, array $opti
}
$body = [
- self::OPTION_COLLECTION => $collectionId,
+ self::OPTION_COLLECTION => $this->makeCollection($collection),
self::OPTION_EXAMPLE => $example->getAllAsObject(['_ignoreHiddenAttributes' => true]),
self::OPTION_NEW_VALUE => $newValue->getAllAsObject(['_ignoreHiddenAttributes' => true])
];
@@ -1591,7 +1692,7 @@ public function replaceByExample($collectionId, $example, $newValue, array $opti
*
* @throws Exception
*
- * @param mixed $collectionId - collection id as string or number
+ * @param mixed $collection - collection as string or object
* @param mixed $document - the example document as a Document object or an array
* @param array $options - optional - an array of options.
*
Options are :
@@ -1606,7 +1707,7 @@ public function replaceByExample($collectionId, $example, $newValue, array $opti
*
* @since 1.2
*/
- public function removeByExample($collectionId, $document, array $options = [])
+ public function removeByExample($collection, $document, array $options = [])
{
$_documentClass = $this->_documentClass;
@@ -1619,7 +1720,7 @@ public function removeByExample($collectionId, $document, array $options = [])
}
$body = [
- self::OPTION_COLLECTION => $collectionId,
+ self::OPTION_COLLECTION => $this->makeCollection($collection),
self::OPTION_EXAMPLE => $document->getAllAsObject(['_ignoreHiddenAttributes' => true])
];
@@ -1652,7 +1753,7 @@ public function removeByExample($collectionId, $document, array $options = [])
*
* @throws Exception
*
- * @param mixed $collectionId - collection id as string or number
+ * @param mixed $collection - collection as string or object
* @param array $keys - array of document keys
* @param array $options - optional - an array of options.
*
Options are :
@@ -1666,10 +1767,10 @@ public function removeByExample($collectionId, $document, array $options = [])
*
* @since 2.6
*/
- public function removeByKeys($collectionId, array $keys, array $options = [])
+ public function removeByKeys($collection, array $keys, array $options = [])
{
$body = [
- self::OPTION_COLLECTION => $collectionId,
+ self::OPTION_COLLECTION => $this->makeCollection($collection),
self::OPTION_KEYS => $keys
];
@@ -1705,7 +1806,7 @@ public function removeByKeys($collectionId, array $keys, array $options = [])
*
* @throws Exception
*
- * @param mixed $collectionId - collection id as string or number
+ * @param mixed $collection - collection as string or object
* @param array $keys - array of document keys
* @param array $options - optional array of options.
*
Options are :
@@ -1718,12 +1819,12 @@ public function removeByKeys($collectionId, array $keys, array $options = [])
*
* @since 2.6
*/
- public function lookupByKeys($collectionId, array $keys, array $options = [])
+ public function lookupByKeys($collection, array $keys, array $options = [])
{
$_documentClass = $this->_documentClass;
$body = [
- self::OPTION_COLLECTION => $collectionId,
+ self::OPTION_COLLECTION => $this->makeCollection($collection),
self::OPTION_KEYS => $keys
];
@@ -1748,7 +1849,7 @@ public function lookupByKeys($collectionId, array $keys, array $options = [])
*
* @throws Exception
*
- * @param mixed $collectionId - collection id as string or number
+ * @param mixed $collection - collection as string or object
* @param string $attribute - the attribute path , like 'a', 'a.b', etc...
* @param mixed $left - The lower bound.
* @param mixed $right - The upper bound.
@@ -1769,9 +1870,11 @@ public function lookupByKeys($collectionId, array $keys, array $options = [])
*
*
*
+ * @deprecated use AQL queries instead
+ *
* @return Cursor - documents matching the example [0...n]
*/
- public function range($collectionId, $attribute, $left, $right, array $options = [])
+ public function range($collection, $attribute, $left, $right, array $options = [])
{
if ($attribute === '') {
throw new ClientException('Invalid attribute specification');
@@ -1783,7 +1886,7 @@ public function range($collectionId, $attribute, $left, $right, array $options =
}
$body = [
- self::OPTION_COLLECTION => $collectionId,
+ self::OPTION_COLLECTION => $this->makeCollection($collection),
self::OPTION_ATTRIBUTE => $attribute,
self::OPTION_LEFT => $left,
self::OPTION_RIGHT => $right
@@ -1812,10 +1915,9 @@ public function range($collectionId, $attribute, $left, $right, array $options =
*
* This will throw if the list cannot be fetched from the server
*
- *
* @throws Exception
*
- * @param mixed $collectionId - collection id as string or number
+ * @param mixed $collection - collection as string or object
* @param double $latitude - The latitude of the coordinate.
* @param double $longitude - The longitude of the coordinate.
* @param array $options - optional array of options.
@@ -1835,12 +1937,14 @@ public function range($collectionId, $attribute, $left, $right, array $options =
*
*
*
+ * @deprecated use AQL queries instead
+ *
* @return Cursor - documents matching the example [0...n]
*/
- public function near($collectionId, $latitude, $longitude, array $options = [])
+ public function near($collection, $latitude, $longitude, array $options = [])
{
$body = [
- self::OPTION_COLLECTION => $collectionId,
+ self::OPTION_COLLECTION => $this->makeCollection($collection),
self::OPTION_LATITUDE => $latitude,
self::OPTION_LONGITUDE => $longitude
];
@@ -1871,7 +1975,7 @@ public function near($collectionId, $latitude, $longitude, array $options = [])
*
* @throws Exception
*
- * @param mixed $collectionId - collection id as string or number
+ * @param mixed $collection - collection as string or object
* @param double $latitude - The latitude of the coordinate.
* @param double $longitude - The longitude of the coordinate.
* @param int $radius - The maximal radius (in meters).
@@ -1892,12 +1996,14 @@ public function near($collectionId, $latitude, $longitude, array $options = [])
*
*
*
+ * @deprecated use AQL queries instead
+ *
* @return Cursor - documents matching the example [0...n]
*/
- public function within($collectionId, $latitude, $longitude, $radius, array $options = [])
+ public function within($collection, $latitude, $longitude, $radius, array $options = [])
{
$body = [
- self::OPTION_COLLECTION => $collectionId,
+ self::OPTION_COLLECTION => $this->makeCollection($collection),
self::OPTION_LATITUDE => $latitude,
self::OPTION_LONGITUDE => $longitude,
self::OPTION_RADIUS => $radius
diff --git a/tests/CollectionBasicTest.php b/tests/CollectionBasicTest.php
index 6b408447..bc530ca7 100644
--- a/tests/CollectionBasicTest.php
+++ b/tests/CollectionBasicTest.php
@@ -762,6 +762,125 @@ public function testCreateAndDeleteSystemCollectionWithoutCreatingObject()
$collectionHandler->drop($name, ['isSystem' => true]);
}
+
+ /**
+ * Creates an index using createIndex
+ */
+ public function testCreateIndex()
+ {
+ $result = $this->collectionHandler->createIndex(
+ 'ArangoDB_PHP_TestSuite_IndexTestCollection' . '_' . static::$testsTimestamp, [
+ 'type' => 'hash',
+ 'name' => 'mr-hash',
+ 'fields' => ['a', 'b'],
+ 'unique' => true,
+ 'sparse' => true,
+ 'inBackground' => true
+ ]
+ );
+
+ $indices = $this->collectionHandler->getIndexes('ArangoDB_PHP_TestSuite_IndexTestCollection' . '_' . static::$testsTimestamp);
+
+ $indicesByIdentifiers = $indices['identifiers'];
+
+ static::assertArrayHasKey($result['id'], $indicesByIdentifiers);
+
+ $indexInfo = $indicesByIdentifiers[$result['id']];
+
+ static::assertEquals('hash', $indexInfo[CollectionHandler::OPTION_TYPE]);
+ static::assertEquals(['a', 'b'], $indexInfo['fields']);
+ static::assertTrue($indexInfo['unique']);
+ static::assertTrue($indexInfo['sparse']);
+ static::assertEquals('mr-hash', $indexInfo['name']);
+ }
+
+
+ /**
+ * Gets an index by id
+ */
+ public function testGetIndexById()
+ {
+ $result = $this->collectionHandler->createIndex(
+ 'ArangoDB_PHP_TestSuite_IndexTestCollection' . '_' . static::$testsTimestamp, [
+ 'type' => 'persistent',
+ 'name' => 'abc',
+ 'fields' => ['b', 'a', 'c'],
+ 'unique' => false,
+ 'sparse' => true,
+ 'inBackground' => false
+ ]
+ );
+
+ $indexInfo = $this->collectionHandler->getIndex('ArangoDB_PHP_TestSuite_IndexTestCollection' . '_' . static::$testsTimestamp, $result['id']);
+
+ static::assertEquals('persistent', $indexInfo[CollectionHandler::OPTION_TYPE]);
+ static::assertEquals(['b', 'a', 'c'], $indexInfo['fields']);
+ static::assertFalse($indexInfo['unique']);
+ static::assertTrue($indexInfo['sparse']);
+ static::assertEquals('abc', $indexInfo['name']);
+ }
+
+
+ /**
+ * Gets an index by name
+ */
+ public function testGetIndexByName()
+ {
+ $result = $this->collectionHandler->createIndex(
+ 'ArangoDB_PHP_TestSuite_IndexTestCollection' . '_' . static::$testsTimestamp, [
+ 'type' => 'fulltext',
+ 'name' => 'this-is-an-index',
+ 'fields' => ['c'],
+ 'minLength' => 4,
+ ]
+ );
+
+ $indexInfo = $this->collectionHandler->getIndex('ArangoDB_PHP_TestSuite_IndexTestCollection' . '_' . static::$testsTimestamp, $result['id']);
+
+ static::assertEquals('fulltext', $indexInfo[CollectionHandler::OPTION_TYPE]);
+ static::assertEquals(['c'], $indexInfo['fields']);
+ static::assertFalse($indexInfo['unique']);
+ static::assertTrue($indexInfo['sparse']);
+ static::assertEquals(4, $indexInfo['minLength']);
+ static::assertEquals('this-is-an-index', $indexInfo['name']);
+ }
+
+ /**
+ * Drops an index by id
+ */
+ public function testDropIndexById()
+ {
+ $result = $this->collectionHandler->createIndex(
+ 'ArangoDB_PHP_TestSuite_IndexTestCollection' . '_' . static::$testsTimestamp, [
+ 'type' => 'fulltext',
+ 'name' => 'this-is-an-index',
+ 'fields' => ['c'],
+ 'minLength' => 4,
+ ]
+ );
+
+ $result = $this->collectionHandler->dropIndex('ArangoDB_PHP_TestSuite_IndexTestCollection' . '_' . static::$testsTimestamp, $result['id']);
+ static::assertTrue($result);
+ }
+
+
+ /**
+ * Drops an index by name
+ */
+ public function testDropIndexByName()
+ {
+ $result = $this->collectionHandler->createIndex(
+ 'ArangoDB_PHP_TestSuite_IndexTestCollection' . '_' . static::$testsTimestamp, [
+ 'type' => 'fulltext',
+ 'name' => 'this-is-an-index',
+ 'fields' => ['c'],
+ 'minLength' => 4,
+ ]
+ );
+
+ $result = $this->collectionHandler->dropIndex('ArangoDB_PHP_TestSuite_IndexTestCollection' . '_' . static::$testsTimestamp, 'this-is-an-index');
+ static::assertTrue($result);
+ }
/**
* Create a geo index with 1 field and verify it by getting information about the index from the server
@@ -1146,6 +1265,85 @@ public function testHasCollectionReturnsTrueIfCollectionExists()
{
static::assertTrue($this->collectionHandler->has('ArangoDB_PHP_TestSuite_IndexTestCollection' . '_' . static::$testsTimestamp));
}
+
+
+ /**
+ * get shards
+ */
+ public function testGetShards()
+ {
+ if (!isCluster($this->connection)) {
+ // don't execute this test in a non-cluster
+ $this->markTestSkipped("test is only meaningful in cluster");
+ return;
+ }
+
+ $connection = $this->connection;
+ $collection = new Collection();
+ $collectionHandler = new CollectionHandler($connection);
+
+ $name = 'ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp;
+
+ try {
+ $collectionHandler->drop($name);
+ } catch (Exception $e) {
+ //Silence the exception
+ }
+
+ $collection->setName($name);
+ $collection->setNumberOfShards(5);
+
+ $collectionHandler->create($collection);
+
+ $shardIds = $collectionHandler->getShards($collection);
+ static::assertEquals(5, count($shardIds));
+
+ foreach ($shardIds as $shardId) {
+ static::assertTrue(is_string($shardId));
+ }
+ }
+
+ /**
+ * find responsible shard
+ */
+ public function testGetResponsibleShard()
+ {
+ if (!isCluster($this->connection)) {
+ // don't execute this test in a non-cluster
+ $this->markTestSkipped("test is only meaningful in cluster");
+ return;
+ }
+
+ $connection = $this->connection;
+ $collection = new Collection();
+ $collectionHandler = new CollectionHandler($connection);
+ $documentHandler = new DocumentHandler($connection);
+
+ $name = 'ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp;
+
+ try {
+ $collectionHandler->drop($name);
+ } catch (Exception $e) {
+ //Silence the exception
+ }
+
+ $collection->setName($name);
+ $collection->setNumberOfShards(5);
+
+ $response = $collectionHandler->create($collection);
+
+ $shardIds = $collectionHandler->getShards($collection);
+
+ for ($i = 0; $i < 100; ++$i) {
+ $doc = new Document();
+ $doc->setInternalKey('test' . $i);
+
+ $documentHandler->save($collection, $doc);
+
+ $responsible = $collectionHandler->getResponsibleShard($collection, $doc);
+ static::assertTrue(in_array($responsible, $shardIds));
+ }
+ }
public function tearDown()
{
From e5f686cd90150a202f0c7d277cf4af0857ef8a90 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Mon, 5 Aug 2019 17:38:57 +0200
Subject: [PATCH 018/101] update examples
---
examples/aql-query.php | 69 ++++++++++++++++++++++++++++++++
examples/select.php | 39 ------------------
examples/streaming-aql-query.php | 54 +++++++++++++++++++++++++
3 files changed, 123 insertions(+), 39 deletions(-)
create mode 100644 examples/aql-query.php
delete mode 100644 examples/select.php
create mode 100644 examples/streaming-aql-query.php
diff --git a/examples/aql-query.php b/examples/aql-query.php
new file mode 100644
index 00000000..1396eae3
--- /dev/null
+++ b/examples/aql-query.php
@@ -0,0 +1,69 @@
+ null,
+ 'FOR u IN users FILTER u.id == @id RETURN u' => ['id' => 6],
+ 'FOR u IN users FILTER u.id == @id && u.name != @name RETURN u' => ['id' => 1, 'name' => 'fox'],
+];
+
+
+try {
+ $connection = new Connection($connectionOptions);
+ $collectionHandler = new CollectionHandler($connection);
+ $documentHandler = new DocumentHandler($connection);
+
+ // set up a document collection "testCollection"
+ $collection = new Collection('users');
+ try {
+ $collectionHandler->create($collection);
+ } catch (\Exception $e) {
+ // collection may already exist - ignore this error for now
+ //
+ // make sure it is empty
+ $collectionHandler->truncate($collection);
+ }
+
+ $docs = [
+ Document::createFromArray(['name' => 'foo', 'id' => 1]),
+ Document::createFromArray(['name' => 'bar', 'id' => 2]),
+ Document::createFromArray(['name' => 'baz', 'id' => 3]),
+ Document::createFromArray(['name' => 'fox', 'id' => 4]),
+ Document::createFromArray(['name' => 'qaa', 'id' => 5]),
+ Document::createFromArray(['name' => 'qux', 'id' => 6]),
+ Document::createFromArray(['name' => 'quu', 'id' => 7]),
+ ];
+ foreach ($docs as $doc) {
+ $documentHandler->save($collection, $doc);
+ }
+
+ foreach ($statements as $query => $bindVars) {
+ $statement = new Statement($connection, [
+ 'query' => $query,
+ 'count' => true,
+ 'batchSize' => 1000,
+ 'bindVars' => $bindVars,
+ 'sanitize' => true,
+ ]
+ );
+
+ echo 'RUNNING STATEMENT ' . $statement . PHP_EOL;
+
+ $cursor = $statement->execute();
+ foreach ($cursor->getAll() as $doc) {
+ echo '- RETURN VALUE: ' . json_encode($doc) . PHP_EOL;
+ }
+
+ echo PHP_EOL;
+ }
+} catch (ConnectException $e) {
+ print $e . PHP_EOL;
+} catch (ServerException $e) {
+ print $e . PHP_EOL;
+} catch (ClientException $e) {
+ print $e . PHP_EOL;
+}
diff --git a/examples/select.php b/examples/select.php
deleted file mode 100644
index bc5ee880..00000000
--- a/examples/select.php
+++ /dev/null
@@ -1,39 +0,0 @@
- null,
- 'for u in users filter u.id == @id return u' => ['id' => 6],
- 'for u in users filter u.id == @id && u.name != @name return u' => ['id' => 1, 'name' => 'fox'],
-];
-
-
-try {
- $connection = new Connection($connectionOptions);
-
- foreach ($statements as $query => $bindVars) {
- $statement = new Statement($connection, [
- 'query' => $query,
- 'count' => true,
- 'batchSize' => 1000,
- 'bindVars' => $bindVars,
- 'sanitize' => true,
- ]
- );
-
- print $statement . "\n\n";
-
- $cursor = $statement->execute();
- var_dump($cursor->getAll());
- }
-} catch (ConnectException $e) {
- print $e . PHP_EOL;
-} catch (ServerException $e) {
- print $e . PHP_EOL;
-} catch (ClientException $e) {
- print $e . PHP_EOL;
-}
diff --git a/examples/streaming-aql-query.php b/examples/streaming-aql-query.php
new file mode 100644
index 00000000..749f18d5
--- /dev/null
+++ b/examples/streaming-aql-query.php
@@ -0,0 +1,54 @@
+create($collection);
+ } catch (\Exception $e) {
+ // collection may already exist - ignore this error for now
+ //
+ // make sure it is empty
+ $collectionHandler->truncate($collection);
+ }
+
+ $statement = new Statement($connection, [
+ 'query' => 'FOR i IN 1..10000 INSERT { _key: CONCAT("test", i) } INTO @@collection',
+ 'bindVars' => [ '@collection' => 'testCollection' ],
+ ]);
+
+ $statement->execute();
+
+ echo 'COUNT AFTER AQL INSERT: ' . $collectionHandler->count($collection) . PHP_EOL;
+
+ // next query has a potentially huge result - therefore set the "stream" flag
+ $statement = new Statement($connection, [
+ 'query' => 'FOR doc IN @@collection RETURN doc',
+ 'bindVars' => [ '@collection' => 'testCollection' ],
+ 'stream' => true
+ ]);
+
+ $cursor = $statement->execute();
+
+ $counter = 0;
+ foreach ($cursor as $document) {
+ ++$counter;
+ print '- DOCUMENT KEY: ' . $document->getKey() . PHP_EOL;
+ }
+
+ print 'QUERY RETURNED ' . $counter . ' DOCUMENTS' . PHP_EOL;
+
+} catch (ConnectException $e) {
+ print $e . PHP_EOL;
+} catch (ServerException $e) {
+ print $e . PHP_EOL;
+} catch (ClientException $e) {
+ print $e . PHP_EOL;
+}
From c42e98fab90fe6f112d26bb4da8822df382c823c Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Tue, 6 Aug 2019 09:21:52 +0200
Subject: [PATCH 019/101] cosmetic changes
---
CHANGELOG.md | 3 +++
lib/ArangoDBClient/DocumentHandler.php | 22 ++++++++++------------
tests/DocumentBasicTest.php | 4 ++--
3 files changed, 15 insertions(+), 14 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 320392ec..c513fb0a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,9 @@
Release notes for the ArangoDB-PHP driver 3.5.x
===============================================
+Made `DocumentHandler::save()` an alias for `DocumentHandler::insert()`, to more closely
+match the function names used in arangosh/arangod.
+
Added support for streaming transactions (i.e. transactions that can be composed of multiple
operations on the client side piece-by-piece without specifying the full transaction operations
in advance).
diff --git a/lib/ArangoDBClient/DocumentHandler.php b/lib/ArangoDBClient/DocumentHandler.php
index 82ed19a7..b4121bfd 100644
--- a/lib/ArangoDBClient/DocumentHandler.php
+++ b/lib/ArangoDBClient/DocumentHandler.php
@@ -317,7 +317,7 @@ public function store(Document $document, $collection = null, array $options = [
/**
- * save a document to a collection
+ * insert a document into a collection
*
* This will add the document to the collection and return the document's id
*
@@ -339,7 +339,7 @@ public function store(Document $document, $collection = null, array $options = [
* @return mixed - id of document created
* @since 1.0
*/
- public function save($collection, $document, array $options = [])
+ public function insert($collection, $document, array $options = [])
{
$headers = [];
$this->addTransactionHeader($headers, $collection);
@@ -409,11 +409,11 @@ public function save($collection, $document, array $options = [])
/**
* Insert a document into a collection
*
- * This is an alias for save().
+ * This is an alias for insert().
*/
- public function insert($collection, $document, array $options = [])
+ public function save($collection, $document, array $options = [])
{
- return $this->save($collection, $document, $options);
+ return $this->insert($collection, $document, $options);
}
/**
@@ -548,9 +548,9 @@ protected function patch($url, $collection, $documentId, Document $document, arr
/**
* Replace an existing document in a collection, identified by the document itself
*
- * This will update the document on the server
+ * This will replace the document on the server
*
- * This will throw if the document cannot be updated
+ * This will throw if the document cannot be replaced
*
* If policy is set to error (locally or globally through the ConnectionOptions)
* and the passed document has a _rev value set, the database will check
@@ -558,10 +558,10 @@ protected function patch($url, $collection, $documentId, Document $document, arr
*
* @throws Exception
*
- * @param Document $document - document to be updated
+ * @param Document $document - document to be replaced
* @param array $options - optional, array of options
*
Options are :
- *
'policy' - update policy to be used in case of conflict ('error', 'last' or NULL [use default])
+ *
'policy' - replace policy to be used in case of conflict ('error', 'last' or NULL [use default])
*
'waitForSync' - can be used to force synchronisation of the document update operation to disk even in case that the waitForSync flag had been disabled for the entire collection
'createCollection' - true to create the collection if it does not exist
*
'createCollectionType' - "document" or 2 for document collection
*
"edge" or 3 for edge collection
*
'waitForSync' - if set to true, then all removal operations will instantly be synchronised to disk / If this is not specified, then the collection's default sync behavior will be applied.