From ac1cfbd02631d08dae016b2ec099e507b68bb498 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Tue, 20 Apr 2021 16:09:46 +0200
Subject: [PATCH 01/35] fix preview container id
---
tests/travis/setup_arangodb.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/travis/setup_arangodb.sh b/tests/travis/setup_arangodb.sh
index 8a57af44..2950ead8 100644
--- a/tests/travis/setup_arangodb.sh
+++ b/tests/travis/setup_arangodb.sh
@@ -36,7 +36,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $DIR
docker pull arangodb/arangodb-preview:devel-nightly
-docker run -d -e ARANGO_ROOT_PASSWORD="test" -p 8529:8529 arangodb/arangodb-preview:devel-nightly
+docker run -d -e ARANGO_ROOT_PASSWORD="test" -p 8529:8529 arangodb/arangodb-preview:3.9.0-nightly
sleep 2
From 6cb22b17dbbd39ca6cbe8a2771cf2d89c9cb6270 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Thu, 22 Apr 2021 12:47:16 +0200
Subject: [PATCH 02/35] change cursor API from PUT to POST
---
CHANGELOG.md | 4 ++++
lib/ArangoDBClient/Cursor.php | 2 +-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca92ca18..a8b4175b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,10 @@ error message string "precondition failed". This was changed in version 3.7 to r
same error codes, but an error message string of "conflict". Version 3.8 changes this again
so the error message string is now "conflict, _rev values do not match".
+The `Cursor` class will now fetch outstanding cursor result data via HTTP POST requests to
+`/_api/cursor/`. It previously fetched further results via HTTP PUT requests from
+the same address. The change is necessary because fetching further results is not an
+idempotent operation, but the HTTP standard requires PUT operations to be idempotent.
## Release notes for the ArangoDB-PHP driver 3.7.x
diff --git a/lib/ArangoDBClient/Cursor.php b/lib/ArangoDBClient/Cursor.php
index 04fb1bd8..7e946ce1 100644
--- a/lib/ArangoDBClient/Cursor.php
+++ b/lib/ArangoDBClient/Cursor.php
@@ -690,7 +690,7 @@ private function sanitize(array $rows)
private function fetchOutstanding()
{
// continuation
- $response = $this->_connection->put($this->url() . '/' . $this->_id, '', []);
+ $response = $this->_connection->post($this->url() . '/' . $this->_id, '', []);
++$this->_fetches;
$data = $response->getJson();
From 7986a7e077908b0a9a7fea8c4763ebae10a1d77d Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Thu, 22 Apr 2021 14:41:19 +0200
Subject: [PATCH 03/35] add more tests for document APIs
---
CHANGELOG.md | 3 +
lib/ArangoDBClient/Document.php | 2 +-
lib/ArangoDBClient/DocumentHandler.php | 117 ++++++++++-------
lib/ArangoDBClient/Handler.php | 15 +--
tests/CollectionBasicTest.php | 58 +++++++++
tests/DocumentBasicTest.php | 169 +++++++++++++++++++++++--
tests/DocumentExtendedTest.php | 158 ++++++++++++++++++++++-
7 files changed, 457 insertions(+), 65 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a8b4175b..64191c4a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,9 @@ The `Cursor` class will now fetch outstanding cursor result data via HTTP POST r
the same address. The change is necessary because fetching further results is not an
idempotent operation, but the HTTP standard requires PUT operations to be idempotent.
+Extended maximum valid length for collection names to 256, up from 64 before. This follows a
+change in the ArangoDB server.
+
## Release notes for the ArangoDB-PHP driver 3.7.x
The corresponding ArangoDB version, ArangoDB 3.7 has dropped support for the MMFiles storage
diff --git a/lib/ArangoDBClient/Document.php b/lib/ArangoDBClient/Document.php
index f2fc58c1..095c057d 100644
--- a/lib/ArangoDBClient/Document.php
+++ b/lib/ArangoDBClient/Document.php
@@ -639,7 +639,7 @@ public function setInternalId($id)
}
- if (!preg_match('/^[a-zA-Z0-9_-]{1,64}\/' . self::KEY_REGEX_PART . '$/', $id)) {
+ if (!preg_match('/^[a-zA-Z0-9_-]{1,256}\/' . self::KEY_REGEX_PART . '$/', $id)) {
throw new ClientException('Invalid format for document id');
}
diff --git a/lib/ArangoDBClient/DocumentHandler.php b/lib/ArangoDBClient/DocumentHandler.php
index dceb3b5f..5f18fd91 100644
--- a/lib/ArangoDBClient/DocumentHandler.php
+++ b/lib/ArangoDBClient/DocumentHandler.php
@@ -42,10 +42,15 @@ class DocumentHandler extends Handler
const OPTION_EXAMPLE = 'example';
/**
- * overwrite option
+ * overwrite option (deprecated)
*/
const OPTION_OVERWRITE = 'overwrite';
+ /**
+ * overwriteMode option
+ */
+ const OPTION_OVERWRITE_MODE = 'overwriteMode';
+
/**
* option for returning the old document
*/
@@ -55,6 +60,11 @@ class DocumentHandler extends Handler
* option for returning the new document
*/
const OPTION_RETURN_NEW = 'returnNew';
+
+ /**
+ * silent option
+ */
+ const OPTION_SILENT = 'silent';
/**
@@ -331,9 +341,13 @@ public function store(Document $document, $collection = null, array $options = [
* Options are :
*
'createCollection' - create the collection if it does not yet exist.
* '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.
- * 'overwrite' - if set to true, will turn the insert into a replace operation if a document with the specified key already exists.
+ * 'keepNull' - can be used to instruct ArangoDB to delete existing attributes on update instead setting their values to null. Defaults to true (keep attributes when set to null). only useful with overwriteMode = update
+ * 'mergeObjects' - if true, updates to object attributes will merge the previous and the new objects. if false, replaces the object attribute with the new value. only useful with overwriteMode = update
+ * 'overwriteMode' - determines overwrite behavior in case a document with the same _key already exists. possible values: 'ignore', 'update', 'replace', 'conflict'.
+ * 'overwrite' - deprecated: if set to true, will turn the insert into a replace operation if a document with the specified key already exists.
* 'returnNew' - if set to true, then the newly created document will be returned.
- * 'returnOld' - if set to true, then the replaced document will be returned - useful only when using overwrite = true.
+ * 'returnOld' - if set to true, then the updated/replaced document will be returned - useful only when using overwriteMode = insert/update.
+ * 'silent' - whether or not to return information about the created document (e.g. _key and _rev).
*
*
* @return mixed - id of document created
@@ -346,12 +360,17 @@ public function insert($collection, $document, array $options = [])
$collection = $this->makeCollection($collection);
$_documentClass = $this->_documentClass;
+
+ if (!isset($options[self::OPTION_OVERWRITE_MODE]) &&
+ isset($options[self::OPTION_OVERWRITE])) {
+ // map "overwrite" to "overwriteMode"
+ $options[self::OPTION_OVERWRITE_MODE] = $options[self::OPTION_OVERWRITE] ? 'replace' : 'conflict';
+ unset($options[self::OPTION_OVERWRITE]);
+ }
$params = $this->includeOptionsInParams(
$options, [
- 'waitForSync' => null,
- 'silent' => false,
- 'overwrite' => (bool) @$options[self::OPTION_OVERWRITE],
+ 'waitForSync' => $this->getConnectionOption(ConnectionOptions::OPTION_WAIT_SYNC),
'returnOld' => (bool) @$options[self::OPTION_RETURN_OLD],
'returnNew' => (bool) @$options[self::OPTION_RETURN_NEW],
]
@@ -380,8 +399,13 @@ public function insert($collection, $document, array $options = [])
if ($batchPart = $response->getBatchPart()) {
return $batchPart;
}
+
+ if (@$params[self::OPTION_SILENT]) {
+ // nothing will be returned here
+ return null;
+ }
- if (@$options[self::OPTION_RETURN_OLD] || @$options[self::OPTION_RETURN_NEW]) {
+ if ($params[self::OPTION_RETURN_OLD] || $params[self::OPTION_RETURN_NEW]) {
return $json;
}
@@ -438,7 +462,11 @@ public function save($collection, $document, array $options = [])
* Options are :
*
'policy' - update policy to be used in case of conflict ('error', 'last' or NULL [use default])
* 'keepNull' - can be used to instruct ArangoDB to delete existing attributes instead setting their values to null. Defaults to true (keep attributes when set to null)
+ * 'mergeObjects' - if true, updates to object attributes will merge the previous and the new objects. if false, replaces the object attribute with the new value
* '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
+ * 'returnNew' - if set to true, then the updated document will be returned.
+ * 'returnOld' - if set to true, then the previous version of the document will be returned.
+ * 'silent' - whether or not to return information about the created document (e.g. _key and _rev).
*
*
* @return bool - always true, will throw if there is an error
@@ -470,7 +498,11 @@ public function update(Document $document, array $options = [])
* Options are :
*
'policy' - update policy to be used in case of conflict ('error', 'last' or NULL [use default])
* 'keepNull' - can be used to instruct ArangoDB to delete existing attributes instead setting their values to null. Defaults to true (keep attributes when set to null)
+ * 'mergeObjects' - if true, updates to object attributes will merge the previous and the new objects. if false, replaces the object attribute with the new value
* '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
+ * 'returnNew' - if set to true, then the updated document will be returned.
+ * 'returnOld' - if set to true, then the previous version of the document will be returned.
+ * 'silent' - whether or not to return information about the created document (e.g. _key and _rev).
*
*
* @return bool - always true, will throw if there is an error
@@ -491,11 +523,6 @@ public function updateById($collection, $documentId, Document $document, array $
* @param mixed $documentId - document id as string or number
* @param Document $document - patch document which contains the attributes and values to be updated
* @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])
- * 'keepNull' - can be used to instruct ArangoDB to delete existing attributes instead setting their values to null. Defaults to true (keep attributes when set to null)
- * '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
- *
*
* @internal
*
@@ -512,8 +539,6 @@ protected function patch($url, $collection, $documentId, Document $document, arr
$params = $this->includeOptionsInParams(
$options, [
'waitForSync' => $this->getConnectionOption(ConnectionOptions::OPTION_WAIT_SYNC),
- 'keepNull' => true,
- 'silent' => false,
'ignoreRevs' => true,
'policy' => $this->getConnectionOption(ConnectionOptions::OPTION_UPDATE_POLICY),
'returnOld' => (bool) @$options[self::OPTION_RETURN_OLD],
@@ -537,6 +562,12 @@ protected function patch($url, $collection, $documentId, Document $document, arr
$url = UrlHelper::appendParamsUrl($url, $params);
$result = $this->getConnection()->patch($url, $this->json_encode_wrapper($document->getAllForInsertUpdate()), $headers);
+
+ if (@$params[self::OPTION_SILENT]) {
+ // nothing will be returned here
+ return null;
+ }
+
$json = $result->getJson();
$document->setRevision($json[$_documentClass::ENTRY_REV]);
@@ -566,6 +597,11 @@ protected function patch($url, $collection, $documentId, Document $document, arr
* Options are :
*
'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
+ * 'silent' - whether or not to return information about the replaced document (e.g. _key and _rev).
+ * 'ifMatch' - boolean if given revision should match or not
+ * 'revision' - The document is returned if it matches/not matches revision.
+ * 'returnNew' - if set to true, then the replaced document will be returned.
+ * 'returnOld' - if set to true, then the previous version of the document will be returned.
*
*
* @return bool - always true, will throw if there is an error
@@ -596,6 +632,11 @@ public function replace(Document $document, array $options = [])
* Options are :
*
'policy' - update policy to be used in case of conflict ('error', 'last' or NULL [use default])
* 'waitForSync' - can be used to force synchronisation of the document replacement operation to disk even in case that the waitForSync flag had been disabled for the entire collection
+ * 'silent' - whether or not to return information about the replaced document (e.g. _key and _rev).
+ * 'ifMatch' - boolean if given revision should match or not
+ * 'revision' - The document is returned if it matches/not matches revision.
+ * 'returnNew' - if set to true, then the replaced document will be returned.
+ * 'returnOld' - if set to true, then the previous version of the document will be returned.
*
*
* @return bool - always true, will throw if there is an error
@@ -616,11 +657,6 @@ public function replaceById($collection, $documentId, Document $document, array
* @param mixed $documentId - document id as string or number
* @param Document $document - document to be updated
* @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])
- * 'waitForSync' - can be used to force synchronisation of the document replacement operation to disk even in case that the waitForSync flag had been disabled for the entire collection
- * 'ifMatch' - boolean if given revision should match or not
- * 'revision' - The document is returned if it matches/not matches revision.
*
* @internal
*
@@ -637,7 +673,6 @@ protected function put($url, $collection, $documentId, Document $document, array
$params = $this->includeOptionsInParams(
$options, [
'waitForSync' => $this->getConnectionOption(ConnectionOptions::OPTION_WAIT_SYNC),
- 'silent' => false,
'ignoreRevs' => true,
'policy' => $this->getConnectionOption(ConnectionOptions::OPTION_REPLACE_POLICY),
'returnOld' => (bool) @$options[self::OPTION_RETURN_OLD],
@@ -659,6 +694,12 @@ protected function put($url, $collection, $documentId, Document $document, array
$url = UrlHelper::buildUrl($url, [$collection, $documentId]);
$url = UrlHelper::appendParamsUrl($url, $params);
$result = $this->getConnection()->put($url, $this->json_encode_wrapper($data), $headers);
+
+ if (@$params[self::OPTION_SILENT]) {
+ // nothing will be returned here
+ return null;
+ }
+
$json = $result->getJson();
$document->setRevision($json[$_documentClass::ENTRY_REV]);
@@ -680,6 +721,10 @@ protected function put($url, $collection, $documentId, Document $document, array
* Options are :
*
'policy' - update policy to be used in case of conflict ('error', 'last' or NULL [use default])
* 'waitForSync' - can be used to force synchronisation of the document removal operation to disk even in case that the waitForSync flag had been disabled for the entire collection
+ * 'silent' - whether or not to return information about the replaced document (e.g. _key and _rev).
+ * 'ifMatch' - boolean if given revision should match or not
+ * 'revision' - The document is returned if it matches/not matches revision.
+ * 'returnOld' - if set to true, then the previous version of the document will be returned.
*
*
* @return bool - always true, will throw if there is an error
@@ -702,36 +747,15 @@ public function remove(Document $document, array $options = [])
* Options are :
*
'policy' - update policy to be used in case of conflict ('error', 'last' or NULL [use default])
* 'waitForSync' - can be used to force synchronisation of the document removal operation to disk even in case that the waitForSync flag had been disabled for the entire collection
+ * 'silent' - whether or not to return information about the replaced document (e.g. _key and _rev).
+ * 'ifMatch' - boolean if given revision should match or not
+ * 'revision' - The document is returned if it matches/not matches revision.
+ * 'returnOld' - if set to true, then the previous version of the document will be returned.
*
*
* @return bool - always true, will throw if there is an error
*/
public function removeById($collection, $documentId, $revision = null, array $options = [])
- {
- return $this->erase(Urls::URL_DOCUMENT, $collection, $documentId, $revision, $options);
- }
-
-
- /**
- * Remove a document from a collection (internal method)
- *
- * @throws Exception
- *
- * @param string $url - the server-side URL being called
- * @param string $collection - collection id as string or number
- * @param mixed $documentId - document id as string or number
- * @param mixed $revision - optional revision of the document to be deleted
- * @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])
- * 'waitForSync' - can be used to force synchronisation of the document removal operation to disk even in case that the waitForSync flag had been disabled for the entire collection
- *
- *
- * @internal
- *
- * @return bool - always true, will throw if there is an error
- */
- protected function erase($url, $collection, $documentId, $revision = null, array $options = [])
{
$headers = [];
$this->addTransactionHeader($headers, $collection);
@@ -741,7 +765,6 @@ protected function erase($url, $collection, $documentId, $revision = null, array
$params = $this->includeOptionsInParams(
$options, [
'waitForSync' => $this->getConnectionOption(ConnectionOptions::OPTION_WAIT_SYNC),
- 'silent' => false,
'ignoreRevs' => true,
'policy' => $this->getConnectionOption(ConnectionOptions::OPTION_DELETE_POLICY),
'returnOld' => (bool) @$options[self::OPTION_RETURN_OLD],
@@ -758,7 +781,7 @@ protected function erase($url, $collection, $documentId, $revision = null, array
}
}
- $url = UrlHelper::buildUrl($url, [$collection, $documentId]);
+ $url = UrlHelper::buildUrl(Urls::URL_DOCUMENT, [$collection, $documentId]);
$url = UrlHelper::appendParamsUrl($url, $params);
if (@$options[self::OPTION_RETURN_OLD]) {
diff --git a/lib/ArangoDBClient/Handler.php b/lib/ArangoDBClient/Handler.php
index ab5a8e22..15ee4488 100644
--- a/lib/ArangoDBClient/Handler.php
+++ b/lib/ArangoDBClient/Handler.php
@@ -100,19 +100,18 @@ protected function includeOptionsInParams($options, array $includeArray = [])
{
$params = [];
foreach ($options as $key => $value) {
- if (array_key_exists($key, $includeArray)) {
- if ($key === ConnectionOptions::OPTION_UPDATE_POLICY) {
- UpdatePolicy::validate($value);
- }
+ if ($key === ConnectionOptions::OPTION_UPDATE_POLICY) {
+ UpdatePolicy::validate($value);
+ }
+ if ($value === null && isset($includeArray[$key])) {
+ $params[$key] = $includeArray[$key];
+ } else {
$params[$key] = $value;
- if ($value === null) {
- $params[$key] = $includeArray[$key];
- }
}
}
foreach ($includeArray as $key => $value) {
- if (!array_key_exists($key, $options)) {
+ if (!isset($options[$key])) {
if ($key === ConnectionOptions::OPTION_UPDATE_POLICY) {
UpdatePolicy::validate($value);
}
diff --git a/tests/CollectionBasicTest.php b/tests/CollectionBasicTest.php
index 48bd5d72..1576fd7e 100644
--- a/tests/CollectionBasicTest.php
+++ b/tests/CollectionBasicTest.php
@@ -87,6 +87,64 @@ public function testInitializeCollectionWithEdgeType()
static::assertEquals(Collection::TYPE_EDGE, $collection->getType());
}
+
+ /**
+ * Try to create a collection with a long name
+ */
+ public function testCreateCollectionLongName()
+ {
+ $connection = $this->connection;
+ $collection = new Collection();
+ $collectionHandler = new CollectionHandler($connection);
+
+ $name = 'ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp . '_00000000000000000028477732232578523444444444444444444444444444444444442323232';
+ static::assertTrue(strlen($name) > 64);
+
+ try {
+ $collectionHandler->drop($name);
+ } catch (Exception $e) {
+ //Silence the exception
+ }
+
+ $collection->setName($name);
+ $response = $collectionHandler->create($collection);
+
+ static::assertTrue(is_numeric($response), 'Did not return a numeric id!');
+
+ $resultingCollection = $collectionHandler->get($response);
+
+ $resultingAttribute = $resultingCollection->getName();
+ static::assertSame(
+ $name, $resultingAttribute, 'The created collection name and resulting collection name do not match!'
+ );
+
+ static::assertEquals(Collection::getDefaultType(), $resultingCollection->getType());
+
+ $collectionHandler->drop($collection);
+ }
+
+
+ /**
+ * Try to create a collection with a too long name
+ */
+ public function testCreateCollectionTooLongName()
+ {
+ $connection = $this->connection;
+ $collection = new Collection();
+ $collectionHandler = new CollectionHandler($connection);
+
+ $name = 'ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp . str_repeat('x', 256);
+ static::assertTrue(strlen($name) > 256);
+
+ $collection->setName($name);
+ try {
+ $collectionHandler->create($collection);
+ } catch (Exception $exception400) {
+ //Silence the exception
+ }
+
+ static::assertEquals(400, $exception400->getCode());
+ }
/**
diff --git a/tests/DocumentBasicTest.php b/tests/DocumentBasicTest.php
index dde3ef82..eb3d66c8 100644
--- a/tests/DocumentBasicTest.php
+++ b/tests/DocumentBasicTest.php
@@ -63,6 +63,44 @@ public function testInitializeDocument()
}
+ /**
+ * Try to create a document silently
+ */
+ public function testInsertSilent()
+ {
+ $connection = $this->connection;
+ $collection = $this->collection;
+ $document = Document::createFromArray(['_key' => 'me', 'value' => 1]);
+ $documentHandler = new DocumentHandler($connection);
+
+ $document = $documentHandler->insert($collection->getName(), $document, ['silent' => true ]);
+ static::assertNull($document);
+ }
+
+
+ /**
+ * Try to create a document silently - with an error
+ */
+ public function testInsertSilentWithError()
+ {
+ $connection = $this->connection;
+ $collection = $this->collection;
+ $document = Document::createFromArray(['_key' => 'me', 'value' => 1]);
+ $documentHandler = new DocumentHandler($connection);
+
+ // insert the document once
+ $result = $documentHandler->insert($collection->getName(), $document, ['silent' => true ]);
+ static::assertNull($result);
+
+ // and try to insert it again
+ try {
+ $documentHandler->insert($collection->getName(), $document, ['silent' => true ]);
+ } catch (\Exception $exception409) {
+ }
+ static::assertEquals(409, $exception409->getCode());
+ }
+
+
/**
* Try to create a document and return it
*/
@@ -82,7 +120,7 @@ public function testInsertReturnNew()
/**
- * Try to create a document and overwrite it
+ * Try to create a document and overwrite it, using deprecated overwrite option
*/
public function testInsertOverwrite()
{
@@ -91,21 +129,36 @@ public function testInsertOverwrite()
$document = Document::createFromArray(['_key' => 'me', 'value' => 1]);
$documentHandler = new DocumentHandler($connection);
- $document = $documentHandler->insert($collection->getName(), $document, ['returnNew' => true ]);
+ $document = $documentHandler->insert($collection->getName(), $document, ['returnNew' => true]);
static::assertEquals('me', $document['_key']);
static::assertEquals('me', $document['new']['_key']);
static::assertEquals(1, $document['new']['value']);
+ try {
+ $documentHandler->insert($collection->getName(), $document, ['overwrite' => false]);
+ } catch (\Exception $exception409) {
+ }
+ static::assertEquals(409, $exception409->getCode());
+
+ $document = Document::createFromArray(['_key' => 'me', 'value' => 2]);
+ $document = $documentHandler->insert($collection->getName(), $document, ['overwrite' => true, 'returnOld' => true, 'returnNew' => true]);
+ static::assertEquals('me', $document['_key']);
+ static::assertEquals('me', $document['old']['_key']);
+ static::assertEquals('me', $document['new']['_key']);
+ static::assertEquals(1, $document['old']['value']);
+ static::assertEquals(2, $document['new']['value']);
+
+
$document = Document::createFromArray(['_key' => 'other', 'value' => 2]);
- $document = $documentHandler->insert($collection->getName(), $document, ['overwrite' => false, 'returnOld' => true, 'returnNew' => true ]);
+ $document = $documentHandler->insert($collection->getName(), $document, ['overwrite' => false, 'returnOld' => true, 'returnNew' => true]);
static::assertEquals('other', $document['_key']);
static::assertEquals('other', $document['new']['_key']);
static::assertEquals(2, $document['new']['value']);
$document = Document::createFromArray(['_key' => 'other', 'value' => 3]);
- $document = $documentHandler->insert($collection->getName(), $document, ['overwrite' => true, 'returnOld' => true, 'returnNew' => true ]);
+ $document = $documentHandler->insert($collection->getName(), $document, ['overwrite' => true, 'returnOld' => true, 'returnNew' => true]);
static::assertEquals('other', $document['_key']);
static::assertEquals('other', $document['old']['_key']);
@@ -114,12 +167,90 @@ public function testInsertOverwrite()
static::assertEquals(3, $document['new']['value']);
$document = Document::createFromArray(['_key' => 'foo', 'value' => 4]);
- $document = $documentHandler->insert($collection->getName(), $document, ['overwrite' => true, 'returnOld' => true, 'returnNew' => true ]);
+ $document = $documentHandler->insert($collection->getName(), $document, ['overwrite' => true, 'returnOld' => true, 'returnNew' => true]);
static::assertEquals('foo', $document['_key']);
static::assertEquals('foo', $document['new']['_key']);
static::assertEquals(4, $document['new']['value']);
}
+
+ /**
+ * Try to create a document and overwrite it, using overwriteMode option
+ */
+ public function testInsertOverwriteMode()
+ {
+ $connection = $this->connection;
+ $collection = $this->collection;
+ $document = Document::createFromArray(['_key' => 'me', 'value' => 1]);
+ $documentHandler = new DocumentHandler($connection);
+
+ $document = $documentHandler->insert($collection->getName(), $document, ['returnNew' => true]);
+
+ static::assertEquals('me', $document['_key']);
+ static::assertEquals('me', $document['new']['_key']);
+ static::assertEquals(1, $document['new']['value']);
+
+ // conflict mode
+ try {
+ $documentHandler->insert($collection->getName(), $document, ['overwriteMode' => 'conflict']);
+ } catch (\Exception $exception409) {
+ }
+ static::assertEquals(409, $exception409->getCode());
+
+ $document = Document::createFromArray(['_key' => 'other-no-conflict', 'value' => 1]);
+ $document = $documentHandler->insert($collection->getName(), $document, ['overwriteMode' => 'conflict']);
+
+ static::assertEquals($collection->getName() . '/other-no-conflict', $document);
+
+
+ // ignore mode
+ $document = Document::createFromArray(['_key' => 'me', 'value' => 2]);
+ $document = $documentHandler->insert($collection->getName(), $document, ['overwriteMode' => 'ignore', 'returnOld' => true, 'returnNew' => true]);
+
+ static::assertEquals('me', $document['_key']);
+ static::assertFalse(isset($document['_new']));
+ static::assertFalse(isset($document['_old']));
+
+
+ $document = Document::createFromArray(['_key' => 'yet-another', 'value' => 3]);
+ $document = $documentHandler->insert($collection->getName(), $document, ['overwriteMode' => 'ignore', 'returnOld' => true, 'returnNew' => true]);
+
+ static::assertEquals('yet-another', $document['_key']);
+ static::assertEquals('yet-another', $document['new']['_key']);
+ static::assertEquals(3, $document['new']['value']);
+ static::assertFalse(isset($document['_old']));
+
+
+ $document = Document::createFromArray(['_key' => 'yet-another', 'value' => 4]);
+ $document = $documentHandler->insert($collection->getName(), $document, ['overwriteMode' => 'ignore']);
+
+ static::assertEquals($collection->getName() . '/yet-another', $document);
+
+
+ // update mode
+ $document = Document::createFromArray(['_key' => 'me', 'foo' => 'bar']);
+ $document = $documentHandler->insert($collection->getName(), $document, ['overwriteMode' => 'update', 'returnOld' => true, 'returnNew' => true ]);
+
+ static::assertEquals('me', $document['_key']);
+ static::assertEquals('me', $document['old']['_key']);
+ static::assertEquals(1, $document['old']['value']);
+ static::assertEquals('me', $document['new']['_key']);
+ static::assertEquals(1, $document['new']['value']);
+ static::assertEquals('bar', $document['new']['foo']);
+
+
+ // replace mode
+ $document = Document::createFromArray(['_key' => 'me', 'qux' => 'qaz']);
+ $document = $documentHandler->insert($collection->getName(), $document, ['overwriteMode' => 'replace', 'returnOld' => true, 'returnNew' => true ]);
+
+ static::assertEquals('me', $document['_key']);
+ static::assertEquals('me', $document['new']['_key']);
+ static::assertEquals(1, $document['old']['value']);
+ static::assertEquals('bar', $document['old']['foo']);
+ static::assertFalse(isset($document['new']['foo']));
+ static::assertFalse(isset($document['new']['value']));
+ static::assertEquals('qaz', $document['new']['qux']);
+ }
/**
@@ -142,7 +273,7 @@ public function testCreateAndDeleteDocumentWithId()
$id = $resultingDocument->getHandle();
static::assertSame($collection->getName() . '/' . $key, $id);
- $documentHandler->remove($document);
+ static::assertTrue($documentHandler->remove($document));
}
@@ -165,7 +296,30 @@ public function testCreateAndDeleteDocument()
$resultingAttribute = $resultingDocument->someAttribute;
static::assertSame('someValue', $resultingAttribute, 'Resulting Attribute should be "someValue". It\'s :' . $resultingAttribute);
- $documentHandler->remove($document);
+ static::assertTrue($documentHandler->remove($document));
+ }
+
+
+ /**
+ * Try to create and silently delete a document
+ */
+ public function testCreateAndDeleteDocumentSilent()
+ {
+ $connection = $this->connection;
+ $collection = $this->collection;
+ $document = new Document();
+ $documentHandler = new DocumentHandler($connection);
+
+ $document->someAttribute = 'someValue';
+
+ $documentId = $documentHandler->save($collection->getName(), $document);
+
+ $resultingDocument = $documentHandler->get($collection->getName(), $documentId);
+
+ $resultingAttribute = $resultingDocument->someAttribute;
+ static::assertSame('someValue', $resultingAttribute, 'Resulting Attribute should be "someValue". It\'s :' . $resultingAttribute);
+
+ static::assertTrue($documentHandler->remove($document, ['silent' => true]));
}
@@ -231,7 +385,6 @@ public function testCreateAndDeleteDocumentWithoutCreatedCollectionAndOptionCrea
}
-
/**
* Try to create and delete a document using a defined key
*/
diff --git a/tests/DocumentExtendedTest.php b/tests/DocumentExtendedTest.php
index 5c20ad85..810eec41 100644
--- a/tests/DocumentExtendedTest.php
+++ b/tests/DocumentExtendedTest.php
@@ -321,6 +321,66 @@ public function testUpdateDocumentWithWrongEncoding()
$response = $documentHandler->remove($resultingDocument);
static::assertTrue($response, 'Delete should return true!');
}
+
+ /**
+ * test for updating a document using update()
+ */
+ public function testUpdateDocumentMergeObjects()
+ {
+ $documentHandler = $this->documentHandler;
+
+ $document = Document::createFromArray(
+ ['someAttribute' => ['foo' => 'bar', 'bark' => 'qux']]
+ );
+ $documentId = $documentHandler->save($this->collection->getName(), $document);
+ @list(, $documentId) = explode('/', $documentId);
+ static::assertTrue(is_numeric($documentId), 'Did not return an id!');
+
+ $patchDocument = new Document();
+ $patchDocument->set('_id', $document->getHandle());
+ $patchDocument->set('_rev', $document->getRevision());
+ $patchDocument->set('someAttribute', ['piff' => 'paff']);
+ $result = $documentHandler->update($patchDocument, ['mergeObjects' => true]);
+
+ static::assertTrue($result);
+
+ $resultingDocument = $documentHandler->get($this->collection->getName(), $documentId);
+ static::assertObjectHasAttribute('_id', $resultingDocument, '_id field should exist, empty or with an id');
+
+ static::assertEquals(['foo' => 'bar', 'bark' => 'qux', 'piff' => 'paff'], $resultingDocument->someAttribute);
+ $response = $documentHandler->remove($resultingDocument);
+ static::assertTrue($response, 'Delete should return true!');
+ }
+
+ /**
+ * test for updating a document using update()
+ */
+ public function testUpdateDocumentDoNotMergeObjects()
+ {
+ $documentHandler = $this->documentHandler;
+
+ $document = Document::createFromArray(
+ ['someAttribute' => ['foo' => 'bar', 'bark' => 'qux']]
+ );
+ $documentId = $documentHandler->save($this->collection->getName(), $document);
+ @list(, $documentId) = explode('/', $documentId);
+ static::assertTrue(is_numeric($documentId), 'Did not return an id!');
+
+ $patchDocument = new Document();
+ $patchDocument->set('_id', $document->getHandle());
+ $patchDocument->set('_rev', $document->getRevision());
+ $patchDocument->set('someAttribute', ['piff' => 'paff']);
+ $result = $documentHandler->update($patchDocument, ['mergeObjects' => false]);
+
+ static::assertTrue($result);
+
+ $resultingDocument = $documentHandler->get($this->collection->getName(), $documentId);
+ static::assertObjectHasAttribute('_id', $resultingDocument, '_id field should exist, empty or with an id');
+
+ static::assertEquals(['piff' => 'paff'], $resultingDocument->someAttribute);
+ $response = $documentHandler->remove($resultingDocument);
+ static::assertTrue($response, 'Delete should return true!');
+ }
/**
@@ -384,6 +444,54 @@ public function testUpdateDocumentReturnOldNew()
static::assertEquals(2, $result['new']['value']);
static::assertNotEquals($result['old']['_rev'], $result['new']['_rev']);
}
+
+
+ /**
+ * test for silently updating a document
+ */
+ public function testUpdateDocumentSilent()
+ {
+ $documentHandler = $this->documentHandler;
+
+ $document = Document::createFromArray(
+ ['_key' => 'test', 'value' => 1]
+ );
+ $documentHandler->insert($this->collection->getName(), $document);
+
+ $patchDocument = new Document();
+ $patchDocument->set('_id', $document->getHandle());
+ $patchDocument->set('value', 2);
+ $result = $documentHandler->update($patchDocument, ['silent' => true]);
+ static::assertNull($result);
+
+
+ $resultingDocument = $documentHandler->get($this->collection->getName(), 'test');
+ static::assertEquals(2, $resultingDocument->value);
+ }
+
+
+ /**
+ * test for silently updating a document
+ */
+ public function testUpdateDocumentSilentWithError()
+ {
+ $documentHandler = $this->documentHandler;
+
+ $document = Document::createFromArray(
+ ['_key' => 'test', 'value' => 1]
+ );
+ $documentHandler->insert($this->collection->getName(), $document);
+
+ $patchDocument = Document::createFromArray(
+ ['_id' => $this->collection->getName() . '/test-does-not-exist']
+ );
+
+ try {
+ $documentHandler->update($patchDocument, ['silent' => true]);
+ } catch (\Exception $exception404) {
+ }
+ static::assertEquals(404, $exception404->getCode());
+ }
/**
@@ -488,6 +596,55 @@ public function testReplaceDocumentReturnOldNew()
static::assertEquals(2, $result['new']['value']);
static::assertNotEquals($result['old']['_rev'], $result['new']['_rev']);
}
+
+
+ /**
+ * test for silently replacing a document
+ */
+ public function testReplaceDocumentSilent()
+ {
+ $documentHandler = $this->documentHandler;
+
+ $document = Document::createFromArray(
+ ['_key' => 'test', 'value' => 1]
+ );
+ $documentHandler->insert($this->collection->getName(), $document);
+
+ $patchDocument = new Document();
+ $patchDocument->set('_id', $document->getHandle());
+ $patchDocument->set('value', 2);
+ $result = $documentHandler->replace($patchDocument, ['silent' => true]);
+ static::assertNull($result);
+
+
+ $resultingDocument = $documentHandler->get($this->collection->getName(), 'test');
+ static::assertEquals(2, $resultingDocument->value);
+ }
+
+
+ /**
+ * test for silently replacing a document
+ */
+ public function testReplaceDocumentSilentWithError()
+ {
+ $documentHandler = $this->documentHandler;
+
+ $document = Document::createFromArray(
+ ['_key' => 'test', 'value' => 1]
+ );
+ $documentHandler->insert($this->collection->getName(), $document);
+
+ $patchDocument = Document::createFromArray(
+ ['_id' => $this->collection->getName() . '/test-does-not-exist']
+ );
+
+ try {
+ $documentHandler->replace($patchDocument, ['silent' => true]);
+ } catch (\Exception $exception404) {
+ }
+ static::assertEquals(404, $exception404->getCode());
+ }
+
/**
* test for deletion of a document with deleteById() not giving the revision
@@ -540,7 +697,6 @@ public function testDeleteDocumentWithDeleteByIdWithRevisionAndPolicyIsError()
try {
$documentHandler->removeById($this->collection->getName(), $documentId, '_UOarUR----', ['policy' => 'error']);
} catch (ServerException $e) {
- static::assertTrue(true);
}
$response = $documentHandler->removeById($this->collection->getName(), $documentId, $revision, ['policy' => 'error']);
From cd4ed23ca08be443245434ff0fd213e7c7f3ee85 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Thu, 22 Apr 2021 14:49:28 +0200
Subject: [PATCH 04/35] deprecate some functionality
---
CHANGELOG.md | 16 +++++-----------
lib/ArangoDBClient/CollectionHandler.php | 8 ++++++--
lib/ArangoDBClient/Traversal.php | 1 +
3 files changed, 12 insertions(+), 13 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 64191c4a..867bdd13 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,17 +2,6 @@
## Release notes for the ArangoDB-PHP driver 3.8.x
-This version of the PHP driver removes support for the MMFiles storage engine, which was
-deprecated in the arangod server in version 3.6.0, and removed in version 3.7.0..
-
-Updating, replacing or removing documents in the database using a revision id guard value
-may return a different error message in case the revision id value of the found document is
-not as expected.
-Previous versions before 3.7 returned HTTP error 412, an ArangoDB error code 1200 and the
-error message string "precondition failed". This was changed in version 3.7 to return the
-same error codes, but an error message string of "conflict". Version 3.8 changes this again
-so the error message string is now "conflict, _rev values do not match".
-
The `Cursor` class will now fetch outstanding cursor result data via HTTP POST requests to
`/_api/cursor/`. It previously fetched further results via HTTP PUT requests from
the same address. The change is necessary because fetching further results is not an
@@ -21,6 +10,11 @@ idempotent operation, but the HTTP standard requires PUT operations to be idempo
Extended maximum valid length for collection names to 256, up from 64 before. This follows a
change in the ArangoDB server.
+The driver now supports the following options for document CRUD operations:
+- "overwriteMode"
+- "silent"
+- "keepNull", "mergeObjects" (for insert API if overwriteMode=update)
+
## Release notes for the ArangoDB-PHP driver 3.7.x
The corresponding ArangoDB version, ArangoDB 3.7 has dropped support for the MMFiles storage
diff --git a/lib/ArangoDBClient/CollectionHandler.php b/lib/ArangoDBClient/CollectionHandler.php
index 0794a70d..5b51c583 100644
--- a/lib/ArangoDBClient/CollectionHandler.php
+++ b/lib/ArangoDBClient/CollectionHandler.php
@@ -228,7 +228,7 @@ class CollectionHandler extends Handler
const OPTION_FIGURES = 'figures';
/**
- * load option
+ * load option (deprecated)
*/
const OPTION_LOAD = 'load';
@@ -341,7 +341,7 @@ public function create($collection, array $options = [])
}
/**
- * unload option
+ * unload option (deprecated)
*/
const OPTION_UNLOAD = 'unload';
@@ -567,6 +567,8 @@ public function rename($collection, $name)
*
* @param mixed $collection - collection as string or object
*
+ * @deprecated not necessary anymore
+ *
* @return HttpResponse - HTTP response object
*/
public function load($collection)
@@ -588,6 +590,8 @@ public function load($collection)
*
* @param mixed $collection - collection as string or object
*
+ * @deprecated not necessary anymore
+ *
* @return HttpResponse - HTTP response object
*/
public function unload($collection)
diff --git a/lib/ArangoDBClient/Traversal.php b/lib/ArangoDBClient/Traversal.php
index 545af584..888b4c6b 100644
--- a/lib/ArangoDBClient/Traversal.php
+++ b/lib/ArangoDBClient/Traversal.php
@@ -20,6 +20,7 @@
*
* @link https://www.arangodb.com/docs/stable/http/traversal.html
*
+ * @deprecated use AQL traversals instead
* @package ArangoDBClient
* @since 1.4
*/
From 42b7393671bb8c4f71a47fb5e7aff8a8b0ab6e12 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Thu, 22 Apr 2021 18:31:01 +0200
Subject: [PATCH 05/35] added metrics API
---
lib/ArangoDBClient/AdminHandler.php | 102 ++++++++++++++++++++++++++++
lib/ArangoDBClient/Urls.php | 13 ++--
tests/AdminTest.php | 29 ++++++++
3 files changed, 140 insertions(+), 4 deletions(-)
diff --git a/lib/ArangoDBClient/AdminHandler.php b/lib/ArangoDBClient/AdminHandler.php
index c80ef3a3..1d86c991 100644
--- a/lib/ArangoDBClient/AdminHandler.php
+++ b/lib/ArangoDBClient/AdminHandler.php
@@ -159,6 +159,8 @@ public function getServerLog(array $options = [])
*
* @throws Exception
*
+ * @deprecated not necessary anymore
+ *
* @return bool
* @since 1.2
*/
@@ -168,6 +170,102 @@ public function reloadServerRouting()
return true;
}
+
+
+ /**
+ * Get the server metrics
+ * Returns the server metrics, as a structured array
+ *
+ * @link https://www.arangodb.com/docs/stable/http/administration-and-monitoring.html
+ *
+ * This will throw if the metrics cannot be retrieved
+ *
+ * @throws Exception
+ *
+ * @return array
+ *
+ * @since 3.8
+ */
+ public function getServerMetrics()
+ {
+ $url = UrlHelper::appendParamsUrl(Urls::URL_ADMIN_METRICS, []);
+ $response = $this->getConnection()->get($url);
+
+ $metrics = [];
+
+ foreach (explode("\n", $response->getBody()) as $line) {
+ if (trim($line) == "") {
+ continue;
+ }
+ if ($line[0] == "#") {
+ // type or help
+ if (!preg_match("/^#\s*([^\s]+)\s+([^\s]+)\s+(.*)$/", $line, $matches)) {
+ throw new ClientException('Invalid metrics API output line: "' . $line. '"');
+ }
+
+ $metric = $matches[2];
+ if (!isset($metrics[$metric])) {
+ $metrics[$metric] = ["name" => $metric];
+ }
+
+ $metrics[$metric][strtolower($matches[1])] = $matches[3];
+ } else {
+ // metric value
+ if (!preg_match("/^([^\s]+?)(\{.*?\})?\s+(.+)$\s*$/", $line, $matches)) {
+ throw new ClientException('Invalid metrics API output line: "' . $line. '"');
+ }
+
+ $metric = $matches[1];
+ $sub = null;
+ if (preg_match("/_(sum|count|bucket)$/", $metric, $sub)) {
+ // sum, count, buckets
+ $metric = substr($metric, 0, -1 - strlen($sub[1]));
+ }
+
+ if (!isset($metrics[$metric])) {
+ $metrics[$metric] = [];
+ }
+
+ $le = null;
+ // labels
+ if ($matches[2] != "") {
+ $labels = substr($matches[2], 1, strlen($matches[2]) - 2);
+ foreach (explode(",", $labels) as $label) {
+ $parts = explode("=", $label);
+ $key = trim($parts[0]);
+ $value = trim($parts[1], " \"");
+ if (!isset($metrics[$metric]["labels"])) {
+ $metrics[$metric]["labels"] = [];
+ }
+ if ($key != "le") {
+ $metrics[$metric]["labels"][$key] = $value;
+ } else {
+ $le = $value;
+ }
+ }
+ }
+
+ // cast to number
+ $value = $matches[3] + 0;
+
+ if ($sub == null) {
+ // counter
+ $metrics[$metric]["value"] = $value;
+ } else if ($sub[1] == "bucket") {
+ // bucket value
+ if (!isset($metrics[$metric]["buckets"])) {
+ $metrics[$metric]["buckets"] = [];
+ }
+ $metrics[$metric]["buckets"][$le] = $value;
+ } else {
+ // sum, count
+ $metrics[$metric][$sub[1]] = $value;
+ }
+ }
+ }
+
+ return $metrics;
+ }
/**
@@ -190,6 +288,8 @@ public function reloadServerRouting()
*
* @see getServerStatisticsDescription()
*
+ * @deprecated use metrics API instead
+ *
* @since 1.3
*/
public function getServerStatistics()
@@ -224,6 +324,8 @@ public function getServerStatistics()
*
* @see getServerStatistics()
*
+ * @deprecated use metrics API instead
+ *
* @since 1.3
*/
public function getServerStatisticsDescription(array $options = [])
diff --git a/lib/ArangoDBClient/Urls.php b/lib/ArangoDBClient/Urls.php
index 5b1e7c9d..5fd26138 100644
--- a/lib/ArangoDBClient/Urls.php
+++ b/lib/ArangoDBClient/Urls.php
@@ -199,22 +199,27 @@ abstract class Urls
const URL_ADMIN_LOG = '/_admin/log';
/**
- * base URL part for admin routing reload
+ * base URL part for admin routing reload (deprecated)
*/
const URL_ADMIN_ROUTING_RELOAD = '/_admin/routing/reload';
-
+
/**
* base URL part for admin statistics
*/
+ const URL_ADMIN_METRICS = '/_admin/metrics/v2';
+
+ /**
+ * base URL part for admin statistics (deprecated)
+ */
const URL_ADMIN_STATISTICS = '/_admin/statistics';
/**
- * base URL part for admin statistics-description
+ * base URL part for admin statistics-description (deprecated)
*/
const URL_ADMIN_STATISTICS_DESCRIPTION = '/_admin/statistics-description';
/**
- * base URL part for AQL user functions statistics
+ * base URL part for AQL user functions
*/
const URL_AQL_USER_FUNCTION = '/_api/aqlfunction';
diff --git a/tests/AdminTest.php b/tests/AdminTest.php
index d2e8a4cc..775621f6 100644
--- a/tests/AdminTest.php
+++ b/tests/AdminTest.php
@@ -133,6 +133,35 @@ public function testGetServerLog()
static::assertArrayHasKey('text', $result);
static::assertArrayHasKey('totalAmount', $result);
}
+
+
+ /**
+ * Test if we can get the server metrics
+ */
+ public function testGetServerMetrics()
+ {
+ $result = $this->adminHandler->getServerMetrics();
+
+ static::assertTrue(count($result) > 50, "must have at least 50 metrics");
+
+ static::assertTrue(isset($result["arangodb_server_statistics_server_uptime_total"]));
+ $metric = $result["arangodb_server_statistics_server_uptime_total"];
+ static::assertEquals("arangodb_server_statistics_server_uptime_total", $metric["name"]);
+ static::assertTrue(is_string($metric["help"]));
+ static::assertEquals("counter", $metric["type"]);
+ static::assertTrue(is_numeric($metric["value"]));
+ static::assertTrue($metric["value"] > 0);
+
+ static::assertTrue(isset($result["arangodb_client_connection_statistics_connection_time"]));
+ $metric = $result["arangodb_client_connection_statistics_connection_time"];
+ static::assertEquals("arangodb_client_connection_statistics_connection_time", $metric["name"]);
+ static::assertTrue(is_string($metric["help"]));
+ static::assertEquals("histogram", $metric["type"]);
+ static::assertFalse(isset($metric["value"]));
+ static::assertTrue(is_numeric($metric["count"]));
+ static::assertTrue(is_numeric($metric["sum"]));
+ static::assertTrue(is_array($metric["buckets"]));
+ }
/**
From fc9e4bf8676ae0ab57bbb6f437cda7f0bc511559 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Thu, 22 Apr 2021 18:41:25 +0200
Subject: [PATCH 06/35] added test for memoryLimit
---
tests/StatementTest.php | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/tests/StatementTest.php b/tests/StatementTest.php
index 716d2795..972a3e01 100644
--- a/tests/StatementTest.php
+++ b/tests/StatementTest.php
@@ -498,6 +498,42 @@ public function testTtl()
static::assertTrue($excepted);
}
+ public function testMemoryLimit()
+ {
+ $connection = $this->connection;
+
+ $statement = new Statement(
+ $connection, [
+ 'query' => 'RETURN NOOPT(FOR i IN 1..100000 RETURN CONCAT("testisiteisiitit", i))',
+ '_flat' => true
+ ]
+ );
+ static::assertEquals(0, $statement->getMemoryLimit());
+
+ $cursor = $statement->execute();
+
+ $statement = new Statement(
+ $connection, [
+ 'query' => 'RETURN NOOPT(FOR i IN 1..100000 RETURN CONCAT("testisiteisiitit", i))',
+ 'memoryLimit' => 32768,
+ '_flat' => true
+ ]
+ );
+
+ static::assertEquals(32768, $statement->getMemoryLimit());
+
+ $excepted = false;
+ try {
+ $statement->execute();
+ } catch (ServerException $e) {
+ // resource limit exceeded = 32
+ static::assertEquals(32, $e->getServerCode());
+ $excepted = true;
+ }
+
+ static::assertTrue($excepted);
+ }
+
public function testMaxRuntime()
{
$connection = $this->connection;
From 98c6aeaff03bd52f02f097a11d00a22d99cd5965 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Thu, 22 Apr 2021 19:18:05 +0200
Subject: [PATCH 07/35] schema support
---
CHANGELOG.md | 20 ++++
lib/ArangoDBClient/Collection.php | 132 ++++++++---------------
lib/ArangoDBClient/CollectionHandler.php | 15 +--
tests/CollectionBasicTest.php | 36 -------
tests/CollectionExtendedTest.php | 100 +++++++++++------
tests/StreamingTransactionTest.php | 19 ----
tests/TransactionTest.php | 93 ----------------
7 files changed, 136 insertions(+), 279 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 867bdd13..04de0e28 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,18 @@
## Release notes for the ArangoDB-PHP driver 3.8.x
+In this version of the PHP driver the following classes are deprecated, because their
+corresponding server-side APIs have been deprecated in ArangoDB 3.8:
+
+- class Export, class ExportCursor: use AQL streaming queries instead
+- class Traversal: use AQL traversals instead
+- class Batch, and issuing batch requests via them
+
+In addition, the following functionality is deprecated:
+
+- CollectionHandler::load()
+- CollectionHandler::unload()
+
The `Cursor` class will now fetch outstanding cursor result data via HTTP POST requests to
`/_api/cursor/`. It previously fetched further results via HTTP PUT requests from
the same address. The change is necessary because fetching further results is not an
@@ -15,6 +27,14 @@ The driver now supports the following options for document CRUD operations:
- "silent"
- "keepNull", "mergeObjects" (for insert API if overwriteMode=update)
+The following options have been removed in class Collection:
+- isVolatile
+- journalSize
+
+The following functions have been removed in class Collection:
+- setJournalSize(), getJournalSize()
+- setIsVolatile(), getIsVolatile()
+
## Release notes for the ArangoDB-PHP driver 3.7.x
The corresponding ArangoDB version, ArangoDB 3.7 has dropped support for the MMFiles storage
diff --git a/lib/ArangoDBClient/Collection.php b/lib/ArangoDBClient/Collection.php
index c7c484fb..70b64023 100644
--- a/lib/ArangoDBClient/Collection.php
+++ b/lib/ArangoDBClient/Collection.php
@@ -48,13 +48,6 @@ class Collection
*/
private $_waitForSync;
- /**
- * The collection journalSize value (might be NULL for new collections)
- *
- * @var int - journalSize value
- */
- private $_journalSize;
-
/**
* The collection isSystem value (might be NULL for new collections)
*
@@ -62,13 +55,6 @@ class Collection
*/
private $_isSystem;
- /**
- * The collection isVolatile value (might be NULL for new collections)
- *
- * @var bool - isVolatile value
- */
- private $_isVolatile;
-
/**
* The distributeShardsLike value (might be NULL for new collections)
*
@@ -131,6 +117,13 @@ class Collection
* @var array - keyOptions value
*/
private $_keyOptions;
+
+ /**
+ * The collection schema value
+ *
+ * @var mixed - schema
+ */
+ private $_schema;
/**
* Collection id index
@@ -152,11 +145,6 @@ class Collection
*/
const ENTRY_WAIT_SYNC = 'waitForSync';
- /**
- * Collection 'journalSize' index
- */
- const ENTRY_JOURNAL_SIZE = 'journalSize';
-
/**
* Collection 'status' index
*/
@@ -166,17 +154,17 @@ class Collection
* Collection 'keyOptions' index
*/
const ENTRY_KEY_OPTIONS = 'keyOptions';
+
+ /**
+ * Collection 'schema' index
+ */
+ const ENTRY_SCHEMA = 'schema';
/**
* Collection 'isSystem' index
*/
const ENTRY_IS_SYSTEM = 'isSystem';
- /**
- * Collection 'isVolatile' index
- */
- const ENTRY_IS_VOLATILE = 'isVolatile';
-
/**
* Collection 'distributeShardsLike' index
*/
@@ -315,9 +303,7 @@ public function __clone()
$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;
@@ -325,6 +311,7 @@ public function __clone()
$this->_shardingStrategy = null;
$this->_shardKeys = null;
$this->_smartJoinAttribute = null;
+ $this->_schema = null;
}
/**
@@ -372,12 +359,11 @@ public function getAll()
self::ENTRY_ID => $this->_id,
self::ENTRY_NAME => $this->_name,
self::ENTRY_WAIT_SYNC => $this->_waitForSync,
- self::ENTRY_JOURNAL_SIZE => $this->_journalSize,
self::ENTRY_IS_SYSTEM => $this->_isSystem,
- self::ENTRY_IS_VOLATILE => $this->_isVolatile,
self::ENTRY_TYPE => $this->_type,
self::ENTRY_STATUS => $this->_status,
- self::ENTRY_KEY_OPTIONS => $this->_keyOptions
+ self::ENTRY_KEY_OPTIONS => $this->_keyOptions,
+ self::ENTRY_SCHEMA => $this->_schema
];
if (null !== $this->_distributeShardsLike) {
@@ -407,6 +393,8 @@ public function getAll()
if (null !== $this->_smartJoinAttribute) {
$result[self::ENTRY_SMART_JOIN_ATTRIBUTE] = $this->_smartJoinAttribute;
}
+
+ $result[self::ENTRY_SCHEMA] = $this->_schema;
return $result;
}
@@ -447,21 +435,11 @@ public function set($key, $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;
@@ -477,6 +455,11 @@ public function set($key, $value)
return;
}
+ if ($key === self::ENTRY_SCHEMA) {
+ $this->setSchema($value);
+ return;
+ }
+
if ($key === self::ENTRY_DISTRIBUTE_SHARDS_LIKE) {
$this->setDistributeShardsLike($value);
return;
@@ -579,6 +562,31 @@ public function getName()
return $this->_name;
}
+
+ /**
+ * Set the collection schema
+ *
+ * @param mixed $schema - schema
+ *
+ * @return void
+ */
+ public function setSchema($schema)
+ {
+ assert(is_null($schema) || is_array($schema));
+
+ $this->_schema = $schema;
+ }
+
+ /**
+ * Get the collection schema (if any)
+ *
+ * @return mixed - schema
+ */
+ public function getSchema()
+ {
+ return $this->_schema;
+ }
+
/**
* Set the collection type.
*
@@ -716,29 +724,6 @@ public function getWaitForSync()
return $this->_waitForSync;
}
- /**
- * Set the journalSize value
- *
- * @param int $value - journalSize value
- *
- * @return void
- */
- public function setJournalSize($value)
- {
- assert(is_numeric($value));
- $this->_journalSize = $value;
- }
-
- /**
- * Get the journalSize value (if already known)
- *
- * @return int - journalSize value
- */
- public function getJournalSize()
- {
- return $this->_journalSize;
- }
-
/**
* Set the isSystem value
*
@@ -762,29 +747,6 @@ public function getIsSystem()
return $this->_isSystem;
}
- /**
- * Set the isVolatile value
- *
- * @param bool $value - isVolatile value
- *
- * @return void
- */
- public function setIsVolatile($value)
- {
- assert(null === $value || is_bool($value));
- $this->_isVolatile = $value;
- }
-
- /**
- * Get the isVolatile value (if already known)
- *
- * @return bool - isVolatile value
- */
- public function getIsVolatile()
- {
- return $this->_isVolatile;
- }
-
/**
* Set the distribute shards like value
*
diff --git a/lib/ArangoDBClient/CollectionHandler.php b/lib/ArangoDBClient/CollectionHandler.php
index 5b51c583..f0d4c0bd 100644
--- a/lib/ArangoDBClient/CollectionHandler.php
+++ b/lib/ArangoDBClient/CollectionHandler.php
@@ -246,9 +246,7 @@ class CollectionHandler extends Handler
* 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.
- * 'journalSize' - journalSize value.
* 'isSystem' - false->user collection(default), true->system collection .
- * 'isVolatile' - false->persistent collection(default), true->volatile (in-memory) collection .
* 'keyOptions' - key options to use.
* 'distributeShardsLike' - name of prototype collection for identical sharding.
* 'numberOfShards' - number of shards for the collection.
@@ -257,6 +255,7 @@ class CollectionHandler extends Handler
* 'shardKeys' - array of shard key attributes.
* 'shardingStrategy' - sharding strategy to use in cluster.
* 'smartJoinAttribute' - attribute name for smart joins (if not shard key).
+ * 'schema' - collection schema.
*
*
* @return mixed - id of collection created
@@ -275,27 +274,18 @@ public function create($collection, array $options = [])
$collection->setWaitForSync($this->getConnectionOption(ConnectionOptions::OPTION_WAIT_SYNC));
}
- if ($collection->getJournalSize() === null) {
- $collection->setJournalSize($this->getConnectionOption(ConnectionOptions::OPTION_JOURNAL_SIZE));
- }
-
if ($collection->getIsSystem() === null) {
$collection->setIsSystem($this->getConnectionOption(ConnectionOptions::OPTION_IS_SYSTEM));
}
- if ($collection->getIsVolatile() === null) {
- $collection->setIsVolatile($this->getConnectionOption(ConnectionOptions::OPTION_IS_VOLATILE));
- }
-
$type = $collection->getType() ?: Collection::getDefaultType();
$params = [
Collection::ENTRY_NAME => $collection->getName(),
Collection::ENTRY_TYPE => $type,
Collection::ENTRY_WAIT_SYNC => $collection->getWaitForSync(),
- Collection::ENTRY_JOURNAL_SIZE => $collection->getJournalSize(),
Collection::ENTRY_IS_SYSTEM => $collection->getIsSystem(),
- Collection::ENTRY_IS_VOLATILE => $collection->getIsVolatile(),
Collection::ENTRY_KEY_OPTIONS => $collection->getKeyOptions(),
+ Collection::ENTRY_SCHEMA => $collection->getSchema(),
];
// set extra cluster attributes
@@ -327,6 +317,7 @@ public function create($collection, array $options = [])
$params[Collection::ENTRY_SMART_JOIN_ATTRIBUTE] = $collection->getSmartJoinAttribute();
}
+
$response = $this->getConnection()->post(Urls::URL_COLLECTION, $this->json_encode_wrapper($params));
// $location = $response->getLocationHeader();
diff --git a/tests/CollectionBasicTest.php b/tests/CollectionBasicTest.php
index 1576fd7e..3f9851ee 100644
--- a/tests/CollectionBasicTest.php
+++ b/tests/CollectionBasicTest.php
@@ -39,7 +39,6 @@ public function setUp()
$this->collectionHandler->create('ArangoDB_PHP_TestSuite_IndexTestCollection' . '_' . static::$testsTimestamp);
$adminHandler = new AdminHandler($this->connection);
- $this->isMMFilesEngine = ($adminHandler->getEngine()["name"] == "mmfiles");
}
@@ -750,41 +749,6 @@ public function testCreateAndDeleteEdgeCollectionWithoutCreatingObject()
}
- /**
- * Try to create and delete an edge collection not using an edge object
- */
- public function testCreateAndDeleteVolatileCollectionWithoutCreatingObject()
- {
- if (!$this->isMMFilesEngine) {
- $this->markTestSkipped("test is only meaningful with the mmfiles engine");
- }
-
- $connection = $this->connection;
- $collectionHandler = new CollectionHandler($connection);
-
- $name = 'ArangoDB_PHP_TestSuite_TestCollection_02' . '_' . static::$testsTimestamp;
-
- try {
- $collectionHandler->drop($name);
- } catch (Exception $e) {
- //Silence the exception
- }
-
- $options = ['isVolatile' => true];
- $collectionHandler->create($name, $options);
- $resultingCollection = $collectionHandler->get($name);
-
- $resultingAttribute = $resultingCollection->getName();
- static::assertSame(
- $name, $resultingAttribute, 'The created collection name and resulting collection name do not match!'
- );
- $resultingCollectionProperties = $collectionHandler->getProperties($name);
- static::assertTrue((!$this->isMMFilesEngine) || $resultingCollectionProperties->getIsVolatile());
-
- $collectionHandler->drop($name);
- }
-
-
/**
* Try to create and delete an edge collection not using an edge object
*/
diff --git a/tests/CollectionExtendedTest.php b/tests/CollectionExtendedTest.php
index 7063562d..cb875b01 100644
--- a/tests/CollectionExtendedTest.php
+++ b/tests/CollectionExtendedTest.php
@@ -48,54 +48,101 @@ public function setUp()
}
$adminHandler = new AdminHandler($this->connection);
- $this->isMMFilesEngine = ($adminHandler->getEngine()["name"] == "mmfiles");
}
+
+
+ /**
+ * test for creation with a schema
+ */
+ public function testCreateWithNoSchema()
+ {
+ $collection = $this->collection;
+ $collectionHandler = $this->collectionHandler;
+
+ $resultingAttribute = $collection->getSchema();
+ static::assertNull($resultingAttribute, 'Default schema in collection should be NULL!');
+
+ $name = 'ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp;
+ $collection->setName($name);
+ $response = $collectionHandler->create($collection);
+
+ static::assertTrue(is_numeric($response), 'Adding collection did not return an id!');
+
+ $properties = $collectionHandler->get($name);
+ static::assertNull($properties->getSchema());
+ }
+
+
/**
- * test for creation, get, and delete of a collection with waitForSync default value (no setting)
+ * test for creation with schema
*/
- public function testCreateGetAndDeleteCollectionWithWaitForSyncDefault()
+ public function testCreateWithSchema()
{
$collection = $this->collection;
$collectionHandler = $this->collectionHandler;
- $resultingAttribute = $collection->getWaitForSync();
- static::assertNull($resultingAttribute, 'Default waitForSync in collection should be NULL!');
+ $resultingAttribute = $collection->getSchema();
+ static::assertNull($resultingAttribute, 'Default schema in collection should be NULL!');
$name = 'ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp;
$collection->setName($name);
+ $schema = [
+ "level" => "strict",
+ "rule" => [
+ "type" => "object",
+ "properties" => [
+ "numArray" => [
+ "type" => "array",
+ "items" => [
+ "type" => "number",
+ "maximum" => 6
+ ]
+ ],
+ "name" => [
+ "type" => "string",
+ "minLength" => 4,
+ "maxLength" => 10
+ ],
+ "number" => [
+ "type" => "number",
+ "items" => [
+ "minimum" => 1000000
+ ]
+ ]
+ ],
+ "additionalProperties" => false
+ ],
+ "message" => "Schema validation failed"
+ ];
+
+ $collection->setSchema($schema);
+ static::assertEquals($schema, $collection->getSchema());
$response = $collectionHandler->create($collection);
static::assertTrue(is_numeric($response), 'Adding collection did not return an id!');
- $collectionHandler->get($name);
-
- $response = $collectionHandler->drop($collection);
- static::assertTrue($response, 'Delete should return true!');
+ $collectionWithSchema = $collectionHandler->getProperties($name);
+ static::assertEquals($schema, $collectionWithSchema->getSchema());
}
/**
- * test for creation, getProperties, and delete of a volatile (in-memory-only) collection
+ * test for creation, get, and delete of a collection with waitForSync default value (no setting)
*/
- public function testCreateGetAndDeleteVolatileCollection()
+ public function testCreateGetAndDeleteCollectionWithWaitForSyncDefault()
{
- if (!$this->isMMFilesEngine) {
- $this->markTestSkipped("test is only meaningful with the mmfiles engine");
- }
-
$collection = $this->collection;
$collectionHandler = $this->collectionHandler;
- $resultingAttribute = $collection->getIsVolatile();
- static::assertNull($resultingAttribute, 'Default waitForSync in API should be NULL!');
+ $resultingAttribute = $collection->getWaitForSync();
+ static::assertNull($resultingAttribute, 'Default waitForSync in collection should be NULL!');
$name = 'ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp;
$collection->setName($name);
- $collection->setIsVolatile(true);
$response = $collectionHandler->create($collection);
@@ -104,10 +151,6 @@ public function testCreateGetAndDeleteVolatileCollection()
$collectionHandler->get($name);
- $properties = $collectionHandler->getProperties($name);
- static::assertTrue((!$this->isMMFilesEngine) || $properties->getIsVolatile(), '"isVolatile" should be true!');
-
-
$response = $collectionHandler->drop($collection);
static::assertTrue($response, 'Delete should return true!');
}
@@ -367,18 +410,14 @@ public function testCreateRenameAndDeleteCollectionWithWrongEncoding()
/**
* test for creation, get, and delete of a collection with waitForSync set to true
*/
- public function testCreateGetAndDeleteCollectionWithWaitForSyncTrueAndJournalSizeSet()
+ public function testCreateGetAndDeleteCollectionWithWaitForSyncTrue()
{
$collection = $this->collection;
$collectionHandler = $this->collectionHandler;
$collection->setWaitForSync(true);
- $collection->setJournalSize(1024 * 1024 * 2);
$resultingWaitForSyncAttribute = $collection->getWaitForSync();
- $resultingJournalSizeAttribute = $collection->getJournalSize();
-
static::assertTrue($resultingWaitForSyncAttribute, 'WaitForSync should be true!');
- static::assertEquals(1024 * 1024 * 2, $resultingJournalSizeAttribute, 'JournalSize should be 2MB!');
$name = 'ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp;
$collection->setName($name);
@@ -392,11 +431,6 @@ public function testCreateGetAndDeleteCollectionWithWaitForSyncTrueAndJournalSiz
$properties,
'waiForSync field should exist, empty or with an id'
);
- static::assertObjectHasAttribute(
- '_journalSize',
- $properties,
- 'journalSize field should exist, empty or with an id'
- );
// here we check the collectionHandler->unload() function
// First fill it a bit to make sure it's loaded...
@@ -439,9 +473,7 @@ public function testCreateGetAndDeleteCollectionWithWaitForSyncTrueAndJournalSiz
$resultingWaitForSyncAttribute = $collection->getWaitForSync();
- $resultingJournalSizeAttribute = $collection->getJournalSize();
static::assertTrue($resultingWaitForSyncAttribute, 'Server waitForSync should return true!');
- static::assertEquals(1024 * 1024 * 2, $resultingJournalSizeAttribute, 'JournalSize should be 2MB!');
$response = $collectionHandler->drop($collection);
static::assertTrue($response, 'Delete should return true!');
diff --git a/tests/StreamingTransactionTest.php b/tests/StreamingTransactionTest.php
index 18a50ce7..060cd78a 100644
--- a/tests/StreamingTransactionTest.php
+++ b/tests/StreamingTransactionTest.php
@@ -65,7 +65,6 @@ public function setUp()
$this->collectionHandler->create($this->collection2);
$adminHandler = new AdminHandler($this->connection);
- $this->isMMFilesEngine = ($adminHandler->getEngine()["name"] == "mmfiles");
$this->transactionHandler = new StreamingTransactionHandler($this->connection);
}
@@ -299,9 +298,6 @@ public function testGetCollection()
public function testInsert()
{
- if ($this->isMMFilesEngine) {
- $this->markTestSkipped("test is only meaningful with the rocksdb engine");
- }
$trx = new StreamingTransaction($this->connection, [
TransactionBase::ENTRY_COLLECTIONS => [
TransactionBase::ENTRY_WRITE => [ $this->collection1->getName() ]
@@ -345,9 +341,6 @@ public function testInsert()
public function testRemove()
{
- if ($this->isMMFilesEngine) {
- $this->markTestSkipped("test is only meaningful with the rocksdb engine");
- }
// insert a document before the transaction
$documentHandler = new DocumentHandler($this->connection);
$result = $documentHandler->save($this->collection1->getName(), [ '_key' => 'test', 'value' => 'test' ]);
@@ -406,9 +399,6 @@ public function testRemove()
public function testUpdate()
{
- if ($this->isMMFilesEngine) {
- $this->markTestSkipped("test is only meaningful with the rocksdb engine");
- }
// insert a document before the transaction
$documentHandler = new DocumentHandler($this->connection);
$result = $documentHandler->save($this->collection1->getName(), [ '_key' => 'test', 'value' => 'test' ]);
@@ -459,9 +449,6 @@ public function testUpdate()
public function testReplace()
{
- if ($this->isMMFilesEngine) {
- $this->markTestSkipped("test is only meaningful with the rocksdb engine");
- }
// insert a document before the transaction
$documentHandler = new DocumentHandler($this->connection);
$result = $documentHandler->save($this->collection1->getName(), [ '_key' => 'test', 'value' => 'test' ]);
@@ -516,9 +503,6 @@ public function testReplace()
public function testTruncate()
{
- if ($this->isMMFilesEngine) {
- $this->markTestSkipped("test is only meaningful with the rocksdb engine");
- }
$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() ]
@@ -569,9 +553,6 @@ public function testTruncate()
public function testQuery()
{
- if ($this->isMMFilesEngine) {
- $this->markTestSkipped("test is only meaningful with the rocksdb engine");
- }
$trx = new StreamingTransaction($this->connection, [
TransactionBase::ENTRY_COLLECTIONS => [
TransactionBase::ENTRY_WRITE => [ $this->collection1->getName() ]
diff --git a/tests/TransactionTest.php b/tests/TransactionTest.php
index 98a76950..5a98d666 100644
--- a/tests/TransactionTest.php
+++ b/tests/TransactionTest.php
@@ -60,101 +60,8 @@ public function setUp()
$this->collectionHandler->create($this->collection2);
$adminHandler = new AdminHandler($this->connection);
- $this->isMMFilesEngine = ($adminHandler->getEngine()["name"] == "mmfiles");
}
- /**
- * Test if a deadlock occurs and error 29 is thrown
- */
- public function testDeadlockHandling()
- {
- if (!$this->isMMFilesEngine || isCluster($this->connection)) {
- $this->markTestSkipped("test is only meaningful with the mmfiles engine, single server");
- return;
- }
-
- try {
- $result = $this->connection->post('/_admin/execute', 'return 1');
- } catch (\Exception $e) {
- // /_admin/execute API disabled on the server. must turn on
- // --javascript.allow-admin-execute on the server for this to work
- $this->markTestSkipped("need to start the server with --javascript.allow-admin-execute true to run this test");
- return;
- }
-
- $w1 = [$this->collection1->getName()];
- $action1 = '
- try {
- require("internal").db._executeTransaction({ collections: { write: [ "' . $this->collection2->getName() . '" ] }, action: function () {
- require("internal").wait(7, false);
- var db = require("internal").db;
- db.' . $this->collection1->getName() . '.any();
- }});
- return { message: "ok" };
- } catch (err) {
- return { message: err.errorNum };
- }
- ';
-
- $result1 = $this->connection->post('/_admin/execute?returnAsJSON=true', $action1, ['X-Arango-Async' => 'store']);
- $id1 = $result1->getHeader('x-arango-async-id');
-
- $action2 = '
- try {
- require("internal").db._executeTransaction({ collections: { write: [ "' . $this->collection1->getName() . '" ] }, action: function () {
- require("internal").wait(7, false);
- var db = require("internal").db;
- db.' . $this->collection2->getName() . '.any();
- }});
- return { message: "ok" };
- } catch (err) {
- return { message: err.errorNum };
- }
- ';
-
- $result2 = $this->connection->post('/_admin/execute?returnAsJSON=true', $action2, ['X-Arango-Async' => 'store']);
- $id2 = $result2->getHeader('x-arango-async-id');
-
- $tries = 0;
- $got1 = false;
- $got2 = false;
- $result1 = null;
- $result2 = null;
- while ($tries++ < 20) {
- if (!$got1) {
- try {
- $result1 = $this->connection->put('/_api/job/' . $id1, '');
- if ($result1->getHeader('x-arango-async-id') !== null) {
- $got1 = true;
- }
- } catch (Exception $e) {
- }
- }
- if (!$got2) {
- try {
- $result2 = $this->connection->put('/_api/job/' . $id2, '');
- if ($result2->getHeader('x-arango-async-id') !== null) {
- $got2 = true;
- }
- } catch (Exception $e) {
- }
- }
-
- if ($got1 && $got2) {
- break;
- }
-
- sleep(1);
- }
-
-
- static::assertTrue($got1);
- static::assertTrue($got2);
-
- $r1 = json_decode($result1->getBody());
- $r2 = json_decode($result2->getBody());
- static::assertTrue($r1->message === 29 || $r2->message === 29);
- }
/**
* Test if we can create and execute a transaction by using array initialization at construction time
From 374fe2cd267b05d7ac4646c858024f308cbc492f Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Thu, 22 Apr 2021 19:32:33 +0200
Subject: [PATCH 08/35] more tests for indexes
---
tests/CollectionExtendedTest.php | 159 +++++++++++++++++++++++++++++++
1 file changed, 159 insertions(+)
diff --git a/tests/CollectionExtendedTest.php b/tests/CollectionExtendedTest.php
index cb875b01..c77feb09 100644
--- a/tests/CollectionExtendedTest.php
+++ b/tests/CollectionExtendedTest.php
@@ -2575,6 +2575,165 @@ public function testCreateFulltextIndexedCollectionWithOptions()
$response = $collectionHandler->drop($collection);
static::assertTrue($response, 'Delete should return true!');
}
+
+
+ /**
+ * Test if we can create an array index
+ */
+ public function testCreateArrayIndex()
+ {
+ // set up collections and index
+ $collectionHandler = $this->collectionHandler;
+
+ $collection = Collection::createFromArray(['name' => 'ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp]);
+ $collectionHandler->create($collection);
+
+ $indexRes = $collectionHandler->index(
+ $collection->getName(),
+ 'persistent',
+ ['names[*].first'],
+ );
+
+ static::assertArrayHasKey(
+ 'isNewlyCreated',
+ $indexRes,
+ 'index creation result should have the isNewlyCreated key !'
+ );
+
+ // Check if the index is returned in the indexes of the collection
+ $indexes = $collectionHandler->getIndexes($collection->getName());
+ static::assertSame('names[*].first', $indexes['indexes'][1]['fields'][0]);
+
+ // Drop the index
+ $collectionHandler->dropIndex($indexes['indexes'][1]['id']);
+
+ // Clean up...
+ $response = $collectionHandler->drop($collection);
+ static::assertTrue($response, 'Delete should return true!');
+ }
+
+
+ /**
+ * Test if we can create an array index with deduplicate option
+ */
+ public function testCreateArrayIndexWithDeduplicateOption()
+ {
+ // set up collections and index
+ $collectionHandler = $this->collectionHandler;
+
+ $collection = Collection::createFromArray(['name' => 'ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp]);
+ $collectionHandler->create($collection);
+
+ $indexRes = $collectionHandler->index(
+ $collection->getName(),
+ 'persistent',
+ ['names[*].first'],
+ false,
+ ['deduplicate' => true]
+ );
+
+ static::assertArrayHasKey(
+ 'isNewlyCreated',
+ $indexRes,
+ 'index creation result should have the isNewlyCreated key !'
+ );
+
+ static::assertTrue($indexRes['deduplicate']);
+
+ // Check if the index is returned in the indexes of the collection
+ $indexes = $collectionHandler->getIndexes($collection->getName());
+ static::assertSame('names[*].first', $indexes['indexes'][1]['fields'][0]);
+ static::assertTrue($indexes['indexes'][1]['deduplicate']);
+
+ // Drop the index
+ $collectionHandler->dropIndex($indexes['indexes'][1]['id']);
+
+ // Clean up...
+ $response = $collectionHandler->drop($collection);
+ static::assertTrue($response, 'Delete should return true!');
+ }
+
+
+ /**
+ * Test if we can create an array index with deduplicate option
+ */
+ public function testCreateIndexWithDisabledEstimates()
+ {
+ // set up collections and index
+ $collectionHandler = $this->collectionHandler;
+
+ $collection = Collection::createFromArray(['name' => 'ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp]);
+ $collectionHandler->create($collection);
+
+ $indexRes = $collectionHandler->index(
+ $collection->getName(),
+ 'persistent',
+ ['value'],
+ false,
+ ['estimates' => false]
+ );
+
+ static::assertArrayHasKey(
+ 'isNewlyCreated',
+ $indexRes,
+ 'index creation result should have the isNewlyCreated key !'
+ );
+
+ static::assertFalse($indexRes['estimates']);
+
+ // Check if the index is returned in the indexes of the collection
+ $indexes = $collectionHandler->getIndexes($collection->getName());
+ static::assertSame('value', $indexes['indexes'][1]['fields'][0]);
+ static::assertFalse($indexes['indexes'][1]['estimates']);
+
+ // Drop the index
+ $collectionHandler->dropIndex($indexes['indexes'][1]['id']);
+
+ // Clean up...
+ $response = $collectionHandler->drop($collection);
+ static::assertTrue($response, 'Delete should return true!');
+ }
+
+ /**
+ * Test if we can create a ttl index
+ */
+ public function testCreateTtlIndex()
+ {
+ // set up collections and index
+ $collectionHandler = $this->collectionHandler;
+
+ $collection = Collection::createFromArray(['name' => 'ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp]);
+ $collectionHandler->create($collection);
+
+ $indexRes = $collectionHandler->index(
+ $collection->getName(),
+ 'ttl',
+ ['expiresAt'],
+ false,
+ ['expireAfter' => 10000]
+ );
+
+ static::assertArrayHasKey(
+ 'isNewlyCreated',
+ $indexRes,
+ 'index creation result should have the isNewlyCreated key !'
+ );
+
+ static::assertArrayHasKey('expireAfter', $indexRes);
+ static::assertEquals(10000, $indexRes['expireAfter']);
+
+ // Check if the index is returned in the indexes of the collection
+ $indexes = $collectionHandler->getIndexes($collection->getName());
+ static::assertSame('expiresAt', $indexes['indexes'][1]['fields'][0]);
+ static::assertSame(10000, $indexes['indexes'][1]['expireAfter']);
+
+ // Drop the index
+ $collectionHandler->dropIndex($indexes['indexes'][1]['id']);
+
+ // Clean up...
+ $response = $collectionHandler->drop($collection);
+ static::assertTrue($response, 'Delete should return true!');
+ }
/**
From a570f9bc78dc16ecab38d09ef2652b5db8bd686c Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Thu, 22 Apr 2021 19:48:01 +0200
Subject: [PATCH 09/35] added engine stats
---
lib/ArangoDBClient/AdminHandler.php | 18 +++++++++++++++++-
lib/ArangoDBClient/Urls.php | 5 +++++
tests/AdminTest.php | 24 ++++++++++++++++++++++++
3 files changed, 46 insertions(+), 1 deletion(-)
diff --git a/lib/ArangoDBClient/AdminHandler.php b/lib/ArangoDBClient/AdminHandler.php
index 1d86c991..8637a60b 100644
--- a/lib/ArangoDBClient/AdminHandler.php
+++ b/lib/ArangoDBClient/AdminHandler.php
@@ -29,7 +29,7 @@ class AdminHandler extends Handler
const OPTION_DETAILS = 'details';
/**
- * Get the server's storage engine
+ * Get the server's storage engine
*
* This will throw if the engine data cannot be retrieved
*
@@ -43,6 +43,22 @@ public function getEngine()
$response = $this->getConnection()->get(Urls::URL_ENGINE);
return $response->getJson();
}
+
+ /**
+ * Get the server's storage engine statistics
+ *
+ * This will throw if the engine data cannot be retrieved
+ *
+ * @throws Exception
+ *
+ * @return mixed - an object returning the engine statistics
+ * @since 3.8
+ */
+ public function getEngineStats()
+ {
+ $response = $this->getConnection()->get(Urls::URL_ENGINE_STATS);
+ return $response->getJson();
+ }
/**
* Get the server version
diff --git a/lib/ArangoDBClient/Urls.php b/lib/ArangoDBClient/Urls.php
index 5fd26138..cc4bc282 100644
--- a/lib/ArangoDBClient/Urls.php
+++ b/lib/ArangoDBClient/Urls.php
@@ -177,6 +177,11 @@ abstract class Urls
* URL for storage engine
*/
const URL_ENGINE = '/_api/engine';
+
+ /**
+ * URL for storage engine stats
+ */
+ const URL_ENGINE_STATS = '/_api/engine/stats';
/**
* URL for admin version
diff --git a/tests/AdminTest.php b/tests/AdminTest.php
index 775621f6..00733818 100644
--- a/tests/AdminTest.php
+++ b/tests/AdminTest.php
@@ -21,6 +21,30 @@ public function setUp()
$this->connection = getConnection();
$this->adminHandler = new AdminHandler($this->connection);
}
+
+
+ /**
+ * Test if we can get the storage engine
+ */
+ public function testEngine()
+ {
+ $result = $this->adminHandler->getEngine();
+ static::assertEquals("rocksdb", $result["name"]);
+ static::assertTrue(isset($result["supports"]));
+ }
+
+
+ /**
+ * Test if we can get the storage engine statistics
+ */
+ public function testEngineStats()
+ {
+ $result = $this->adminHandler->getEngineStats();
+ static::assertTrue(is_array($result));
+ static::assertTrue(isset($result["cache.limit"]));
+ static::assertTrue(isset($result["cache.allocated"]));
+ static::assertTrue(isset($result["columnFamilies"]));
+ }
/**
From 13d246892c7fc1e2832b7995efdac3a507f6813c Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Thu, 22 Apr 2021 19:58:13 +0200
Subject: [PATCH 10/35] admin/log/entries
---
CHANGELOG.md | 36 ++++++++++++++++----------
lib/ArangoDBClient/AdminHandler.php | 39 +++++++++++++++++++++++++++++
lib/ArangoDBClient/Collection.php | 4 +--
lib/ArangoDBClient/Urls.php | 7 +++++-
tests/AdminTest.php | 19 ++++++++++++++
5 files changed, 89 insertions(+), 16 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 04de0e28..621a94da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,27 @@
## Release notes for the ArangoDB-PHP driver 3.8.x
+The driver now supports the following options for document CRUD operations:
+- "overwriteMode"
+- "silent"
+- "keepNull", "mergeObjects" (for insert API if overwriteMode=update)
+
+Extended maximum valid length for collection names to 256, up from 64 before. This follows a
+change in the ArangoDB server.
+
+Indexes can now be created with "deduplicate" and "estimates" attribute set/not set.
+
+The engine stats API is now supported via `AdminHandler::getEngineStats()`.
+
+The metrics API is now supported via `AdminHandler::getMetrics()`.
+
+Collection objects now support schemas via the `getSchema()`, `setSchema()` methods.
+
+The `Cursor` class will now fetch outstanding cursor result data via HTTP POST requests to
+`/_api/cursor/`. It previously fetched further results via HTTP PUT requests from
+the same address. The change is necessary because fetching further results is not an
+idempotent operation, but the HTTP standard requires PUT operations to be idempotent.
+
In this version of the PHP driver the following classes are deprecated, because their
corresponding server-side APIs have been deprecated in ArangoDB 3.8:
@@ -13,19 +34,8 @@ In addition, the following functionality is deprecated:
- CollectionHandler::load()
- CollectionHandler::unload()
-
-The `Cursor` class will now fetch outstanding cursor result data via HTTP POST requests to
-`/_api/cursor/`. It previously fetched further results via HTTP PUT requests from
-the same address. The change is necessary because fetching further results is not an
-idempotent operation, but the HTTP standard requires PUT operations to be idempotent.
-
-Extended maximum valid length for collection names to 256, up from 64 before. This follows a
-change in the ArangoDB server.
-
-The driver now supports the following options for document CRUD operations:
-- "overwriteMode"
-- "silent"
-- "keepNull", "mergeObjects" (for insert API if overwriteMode=update)
+- AdminHandler::getServerStatistics()
+- AdminHandler::getServerStatisticsDescription()
The following options have been removed in class Collection:
- isVolatile
diff --git a/lib/ArangoDBClient/AdminHandler.php b/lib/ArangoDBClient/AdminHandler.php
index 8637a60b..5383b481 100644
--- a/lib/ArangoDBClient/AdminHandler.php
+++ b/lib/ArangoDBClient/AdminHandler.php
@@ -127,6 +127,44 @@ public function getServerTime()
return $data['time'];
}
+
+
+ /**
+ * Get the server log entries
+ *
+ * This will throw if the log cannot be retrieved
+ *
+ * @throws Exception
+ *
+ * @param array $options - an array of options that define the result-set:
+ *
+ * Options are :
+ *
'upto' - returns all log entries up to a log-level. Note that log-level must be one of:
+ *
+ *
fatal / 0
+ * error / 1
+ * warning / 2
+ * info / 3
+ * debug / 4
+ *
+ * 'level' - limits the log entries to the ones defined in level. Note that `level` and `upto` are mutably exclusive.
+ * 'offset' - skip the first offset entries.
+ * 'size' - limit the number of returned log-entries to size.
+ * 'start' - Returns all log entries such that their log-entry identifier is greater or equal to lid.
+ * 'sort' - Sort the log-entries either ascending if direction is asc, or descending if it is desc according to their lid. Note that the lid imposes a chronological order.
+ * 'search' - Only return the log-entries containing the text string...
+ *
+ *
+ * @return array - an array holding the various attributes of a log: lid, level, timestamp, text and the total amount of log entries before pagination.
+ * @since 1.2
+ */
+ public function getServerLogEntries(array $options = [])
+ {
+ $url = UrlHelper::appendParamsUrl(Urls::URL_ADMIN_LOG_ENTRIES, $options);
+ $response = $this->getConnection()->get($url);
+
+ return $response->getJson();
+ }
/**
@@ -154,6 +192,7 @@ public function getServerTime()
* 'sort' - Sort the log-entries either ascending if direction is asc, or descending if it is desc according to their lid. Note that the lid imposes a chronological order.
* 'search' - Only return the log-entries containing the text string...
*
+ * @deprecated use getServerLogEntries() instead
*
* @return array - an array holding the various attributes of a log: lid, level, timestamp, text and the total amount of log entries before pagination.
* @since 1.2
diff --git a/lib/ArangoDBClient/Collection.php b/lib/ArangoDBClient/Collection.php
index 70b64023..3872cb60 100644
--- a/lib/ArangoDBClient/Collection.php
+++ b/lib/ArangoDBClient/Collection.php
@@ -834,7 +834,7 @@ public function setWriteConcern($value)
*
* @param int $value - write concern value
*
- * @deprecated use setWriteConcern instead
+ * @deprecated use setWriteConcern() instead
* @return void
*/
public function setMinReplicationFactor($value)
@@ -855,7 +855,7 @@ public function getWriteConcern()
/**
* Get the write concern value (if already known). this is an alias only
*
- * @deprecated use getWriteConcern instead
+ * @deprecated use getWriteConcern() instead
* @return mixed - write concern value
*/
public function getMinReplicationFactor()
diff --git a/lib/ArangoDBClient/Urls.php b/lib/ArangoDBClient/Urls.php
index cc4bc282..252e9754 100644
--- a/lib/ArangoDBClient/Urls.php
+++ b/lib/ArangoDBClient/Urls.php
@@ -199,9 +199,14 @@ abstract class Urls
const URL_ADMIN_TIME = '/_admin/time';
/**
- * URL for admin log
+ * URL for admin log (deprecated)
*/
const URL_ADMIN_LOG = '/_admin/log';
+
+ /**
+ * URL for admin log entries
+ */
+ const URL_ADMIN_LOG_ENTRIES = '/_admin/log/entries';
/**
* base URL part for admin routing reload (deprecated)
diff --git a/tests/AdminTest.php b/tests/AdminTest.php
index 00733818..52a33729 100644
--- a/tests/AdminTest.php
+++ b/tests/AdminTest.php
@@ -87,6 +87,25 @@ public function testGetServerTime()
$result = $this->adminHandler->getServerTime();
static::assertTrue(is_float($result), 'Time must be a double (float)!');
}
+
+
+ /**
+ * Test if we can get the server log
+ * Rather dumb tests just checking that an array is returned
+ */
+ public function testGetServerLogEntries()
+ {
+ $result = $this->adminHandler->getServerLogEntries();
+ static::assertTrue(is_array($result), 'Should be an array');
+
+ foreach ($result as $entry) {
+ static::assertArrayHasKey('id', $entry);
+ static::assertArrayHasKey('topc', $entry);
+ static::assertArrayHasKey('level', $entry);
+ static::assertArrayHasKey('date', $entry);
+ static::assertArrayHasKey('message', $entry);
+ }
+ }
/**
From 23d7a59c523818a0050718d37cda0f1b73c4fcfb Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Thu, 22 Apr 2021 20:27:09 +0200
Subject: [PATCH 11/35] add JWT support
---
CHANGELOG.md | 12 +++++++++++
lib/ArangoDBClient/Connection.php | 26 +++++++++++++++++++-----
lib/ArangoDBClient/ConnectionOptions.php | 14 +------------
lib/ArangoDBClient/DefaultValues.php | 10 ---------
4 files changed, 34 insertions(+), 28 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 621a94da..8bd04e23 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,18 @@
## Release notes for the ArangoDB-PHP driver 3.8.x
+The driver now supports connecting via JWT if the server's JWT secret is known.
+In order to use a JWT to connect, set the following value in ConnectionOptions:
+```
+$connectionOptions = [
+ ArangoDBClient\ConnectionOptions::OPTION_DATABASE => '_system', // database name
+ ArangoDBClient\ConnectionOptions::OPTION_ENDPOINT => 'tcp://127.0.0.1:8529', // endpoint to connect to
+ ArangoDBClient\ConnectionOptions::OPTION_AUTH_TYPE => 'Bearer', // authentication via JWT!
+ ArangoDBClient\ConnectionOptions::OPTION_AUTH_USER => 'root', // user name
+ ArangoDBClient\ConnectionOptions::OPTION_AUTH_PASSWD => 'jwt-secret-value', // server's JWT secret value,
+ ];
+```
+
The driver now supports the following options for document CRUD operations:
- "overwriteMode"
- "silent"
diff --git a/lib/ArangoDBClient/Connection.php b/lib/ArangoDBClient/Connection.php
index 5478a324..0910f1de 100644
--- a/lib/ArangoDBClient/Connection.php
+++ b/lib/ArangoDBClient/Connection.php
@@ -425,12 +425,28 @@ private function updateHttpHeader()
}
if (isset($this->_options[ConnectionOptions::OPTION_AUTH_TYPE], $this->_options[ConnectionOptions::OPTION_AUTH_USER])) {
- // add authorization header
- $authorizationValue = base64_encode(
- $this->_options[ConnectionOptions::OPTION_AUTH_USER] . ':' .
- $this->_options[ConnectionOptions::OPTION_AUTH_PASSWD]
- );
+ if ($this->_options[ConnectionOptions::OPTION_AUTH_TYPE] == 'Bearer') {
+ // JWT
+ $base = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);
+ $base64UrlHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($base));
+ $payload = json_encode([
+ 'preferred_username' => $this->_options[ConnectionOptions::OPTION_AUTH_USER],
+ 'iss' => 'arangodb',
+ 'iat' => (int) microtime(true),
+ ]);
+ $base64UrlPayload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($payload));
+ $signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $this->_options[ConnectionOptions::OPTION_AUTH_PASSWD], true);
+ $base64UrlSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));
+ $authorizationValue = $base64UrlHeader . '.' . $base64UrlPayload . '.' . $base64UrlSignature;
+ } else {
+ // HTTP basic authentication
+ $authorizationValue = base64_encode(
+ $this->_options[ConnectionOptions::OPTION_AUTH_USER] . ':' .
+ $this->_options[ConnectionOptions::OPTION_AUTH_PASSWD]
+ );
+ }
+ // add authorization header
$this->_httpHeader .= sprintf(
'Authorization: %s %s%s',
$this->_options[ConnectionOptions::OPTION_AUTH_TYPE],
diff --git a/lib/ArangoDBClient/ConnectionOptions.php b/lib/ArangoDBClient/ConnectionOptions.php
index f68dd1df..679a72d6 100644
--- a/lib/ArangoDBClient/ConnectionOptions.php
+++ b/lib/ArangoDBClient/ConnectionOptions.php
@@ -165,21 +165,11 @@ class ConnectionOptions implements \ArrayAccess
*/
const OPTION_BATCHSIZE = 'batchSize';
- /**
- * Wait for sync index constant
- */
- const OPTION_JOURNAL_SIZE = 'journalSize';
-
/**
* Wait for sync index constant
*/
const OPTION_IS_SYSTEM = 'isSystem';
- /**
- * Wait for sync index constant
- */
- const OPTION_IS_VOLATILE = 'isVolatile';
-
/**
* Authentication user name
*/
@@ -456,9 +446,7 @@ private static function getDefaults()
self::OPTION_REVISION => null,
self::OPTION_WAIT_SYNC => DefaultValues::DEFAULT_WAIT_SYNC,
self::OPTION_BATCHSIZE => null,
- self::OPTION_JOURNAL_SIZE => DefaultValues::DEFAULT_JOURNAL_SIZE,
self::OPTION_IS_SYSTEM => false,
- self::OPTION_IS_VOLATILE => DefaultValues::DEFAULT_IS_VOLATILE,
self::OPTION_CONNECTION => DefaultValues::DEFAULT_CONNECTION,
self::OPTION_TRACE => null,
self::OPTION_ENHANCED_TRACE => false,
@@ -486,7 +474,7 @@ private static function getDefaults()
*/
private static function getSupportedAuthTypes()
{
- return ['Basic'];
+ return ['Basic', 'Bearer'];
}
/**
diff --git a/lib/ArangoDBClient/DefaultValues.php b/lib/ArangoDBClient/DefaultValues.php
index b01d62f0..84b34dde 100644
--- a/lib/ArangoDBClient/DefaultValues.php
+++ b/lib/ArangoDBClient/DefaultValues.php
@@ -51,16 +51,6 @@ abstract class DefaultValues
*/
const DEFAULT_WAIT_SYNC = false;
- /**
- * Default value for collection journal size
- */
- const DEFAULT_JOURNAL_SIZE = 33554432;
-
- /**
- * Default value for isVolatile
- */
- const DEFAULT_IS_VOLATILE = false;
-
/**
* Default value for createCollection (create the collection on the fly when the first document is added to an unknown collection)
*/
From 1c6c7d41ddb80d1ff1c44caefdbb8e4433326d87 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Thu, 22 Apr 2021 20:37:10 +0200
Subject: [PATCH 12/35] fix a test
---
tests/AdminTest.php | 21 +++++++++++++++------
1 file changed, 15 insertions(+), 6 deletions(-)
diff --git a/tests/AdminTest.php b/tests/AdminTest.php
index 52a33729..32fa5ad8 100644
--- a/tests/AdminTest.php
+++ b/tests/AdminTest.php
@@ -41,9 +41,18 @@ public function testEngineStats()
{
$result = $this->adminHandler->getEngineStats();
static::assertTrue(is_array($result));
- static::assertTrue(isset($result["cache.limit"]));
- static::assertTrue(isset($result["cache.allocated"]));
- static::assertTrue(isset($result["columnFamilies"]));
+
+ if (isCluster($this->connection)) {
+ foreach ($result as $server => $entry) {
+ static::assertTrue(isset($entry["cache.limit"]));
+ static::assertTrue(isset($entry["cache.allocated"]));
+ static::assertTrue(isset($entry["columnFamilies"]));
+ }
+ } else {
+ static::assertTrue(isset($result["cache.limit"]));
+ static::assertTrue(isset($result["cache.allocated"]));
+ static::assertTrue(isset($result["columnFamilies"]));
+ }
}
@@ -96,11 +105,11 @@ public function testGetServerTime()
public function testGetServerLogEntries()
{
$result = $this->adminHandler->getServerLogEntries();
- static::assertTrue(is_array($result), 'Should be an array');
+ static::assertTrue(is_array($result['messages']), 'Should be an array');
- foreach ($result as $entry) {
+ foreach ($result['messages'] as $entry) {
static::assertArrayHasKey('id', $entry);
- static::assertArrayHasKey('topc', $entry);
+ static::assertArrayHasKey('topic', $entry);
static::assertArrayHasKey('level', $entry);
static::assertArrayHasKey('date', $entry);
static::assertArrayHasKey('message', $entry);
From 2de4babc7a9a2c8c8233aef4c9df5589d58c0ee8 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Thu, 22 Apr 2021 20:50:56 +0200
Subject: [PATCH 13/35] remove trailing comma
---
tests/CollectionExtendedTest.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/CollectionExtendedTest.php b/tests/CollectionExtendedTest.php
index c77feb09..76455bbb 100644
--- a/tests/CollectionExtendedTest.php
+++ b/tests/CollectionExtendedTest.php
@@ -2591,7 +2591,7 @@ public function testCreateArrayIndex()
$indexRes = $collectionHandler->index(
$collection->getName(),
'persistent',
- ['names[*].first'],
+ ['names[*].first']
);
static::assertArrayHasKey(
From 8a89f82fef79fbb369ff85895fc9a382714b84e9 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Thu, 22 Apr 2021 22:59:52 +0200
Subject: [PATCH 14/35] update package name
---
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 2950ead8..48c68914 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-nightly
-docker run -d -e ARANGO_ROOT_PASSWORD="test" -p 8529:8529 arangodb/arangodb-preview:3.9.0-nightly
+docker pull arangodb/arangodb-preview:3.8.0-nightly
+docker run -d -e ARANGO_ROOT_PASSWORD="test" -p 8529:8529 arangodb/arangodb-preview:3.8.0-nightly
sleep 2
From bfd3c459939c462b2ebc2c69268c0950e8b342f6 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Fri, 23 Apr 2021 14:36:29 +0200
Subject: [PATCH 15/35] added test
---
tests/DocumentBasicTest.php | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/tests/DocumentBasicTest.php b/tests/DocumentBasicTest.php
index eb3d66c8..73f7eb3a 100644
--- a/tests/DocumentBasicTest.php
+++ b/tests/DocumentBasicTest.php
@@ -321,6 +321,25 @@ public function testCreateAndDeleteDocumentSilent()
static::assertTrue($documentHandler->remove($document, ['silent' => true]));
}
+
+
+ /**
+ * Try to create and silently delete a document
+ */
+ public function testDeleteDocumentSilentWithError()
+ {
+ $connection = $this->connection;
+ $collection = $this->collection;
+ $document = new Document();
+ $documentHandler = new DocumentHandler($connection);
+
+ $document = Document::createFromArray(['_key' => 'does-not-exist']);
+ try {
+ $documentHandler->removeById($collection, $document, ['silent' => true]);
+ } catch (\Exception $exception404) {
+ }
+ static::assertEquals(404, $exception404->getCode());
+ }
/**
From cc468cae26a6bcb8d5561713b877daf5064eabd4 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Fri, 23 Apr 2021 14:58:42 +0200
Subject: [PATCH 16/35] CHANGELOG improvements
---
CHANGELOG.md | 24 +++++++++++++++++-------
1 file changed, 17 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8bd04e23..fb842c47 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,16 +3,19 @@
## Release notes for the ArangoDB-PHP driver 3.8.x
The driver now supports connecting via JWT if the server's JWT secret is known.
-In order to use a JWT to connect, set the following value in ConnectionOptions:
+In order to use a JWT to connect, set the following values in ConnectionOptions:
```
$connectionOptions = [
ArangoDBClient\ConnectionOptions::OPTION_DATABASE => '_system', // database name
ArangoDBClient\ConnectionOptions::OPTION_ENDPOINT => 'tcp://127.0.0.1:8529', // endpoint to connect to
+ ...
ArangoDBClient\ConnectionOptions::OPTION_AUTH_TYPE => 'Bearer', // authentication via JWT!
ArangoDBClient\ConnectionOptions::OPTION_AUTH_USER => 'root', // user name
ArangoDBClient\ConnectionOptions::OPTION_AUTH_PASSWD => 'jwt-secret-value', // server's JWT secret value,
];
```
+Note that the server's JWT _secret_, not a generated JWT, must go into the `OPTION_AUTH_PASSWD`
+ConnectionOption.
The driver now supports the following options for document CRUD operations:
- "overwriteMode"
@@ -44,19 +47,26 @@ corresponding server-side APIs have been deprecated in ArangoDB 3.8:
In addition, the following functionality is deprecated:
-- CollectionHandler::load()
-- CollectionHandler::unload()
-- AdminHandler::getServerStatistics()
-- AdminHandler::getServerStatisticsDescription()
+- DocumentHandler::store(): use DocumentHandeler::insert() with overwriteMode instead
+- DocumentHandler::save(): use DocumentHandeler::insert() instead
+- CollectionHandler::load(): should not be necessary anymore
+- CollectionHandler::unload(): should not be necessary anymore
+- AdminHandler::getServerStatistics(): use getServerMetrics() instead
+- AdminHandler::getServerStatisticsDescription(): use getServerMetrics() instead
-The following options have been removed in class Collection:
+The following server-side options have been removed in class Collection:
- isVolatile
- journalSize
-The following functions have been removed in class Collection:
+This also led to the removal of The following functions in class Collection:
- setJournalSize(), getJournalSize()
- setIsVolatile(), getIsVolatile()
+The original driver namespace `\triagens\ArangoDb` was replaced with `\ArangoDBClient`
+for driver version 3.2. Each class exposed by the driver is also exposed via an alias
+to the old namespace. Using the old namespace `\triagens\ArangoDb` is deprecated and will
+not be supported in future versions of the driver.
+
## Release notes for the ArangoDB-PHP driver 3.7.x
The corresponding ArangoDB version, ArangoDB 3.7 has dropped support for the MMFiles storage
From 948c443312544613f27b82016c63f6aa93b631bf Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Fri, 23 Apr 2021 16:19:17 +0200
Subject: [PATCH 17/35] added insertMany method
---
CHANGELOG.md | 5 +
lib/ArangoDBClient/Connection.php | 13 +-
lib/ArangoDBClient/DocumentHandler.php | 159 ++++++++++++++-----
lib/ArangoDBClient/Handler.php | 7 +-
tests/DocumentBasicTest.php | 211 ++++++++++++++++++++++++-
5 files changed, 338 insertions(+), 57 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fb842c47..f3fed620 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,11 @@
## Release notes for the ArangoDB-PHP driver 3.8.x
+Added CollectionHandler::insertMany() method to simplify insertion of multiple documents
+at once. This is now the preferred way of inserting mutliple documents in a single
+request, and will perform much better than using the `Batch` functionality, which is
+now deprecated.
+
The driver now supports connecting via JWT if the server's JWT secret is known.
In order to use a JWT to connect, set the following values in ConnectionOptions:
```
diff --git a/lib/ArangoDBClient/Connection.php b/lib/ArangoDBClient/Connection.php
index 0910f1de..265d43dd 100644
--- a/lib/ArangoDBClient/Connection.php
+++ b/lib/ArangoDBClient/Connection.php
@@ -904,24 +904,19 @@ public static function check_encoding($data)
* internally it calls the check_encoding() method. If that method does not throw
* an Exception, this method will happily return the json_encoded data.
*
- * @param mixed $data the data to encode
- * @param mixed $options the options for the json_encode() call
+ * @param mixed $data the data to encode
+ * @param bool $forceObjects whether or not to force JSON objects on empty data
*
* @return string the result of the json_encode
* @throws \ArangoDBClient\ClientException
*/
- public function json_encode_wrapper($data, $options = 0)
+ public function json_encode_wrapper($data, $forceObjects = true)
{
if ($this->_options[ConnectionOptions::OPTION_CHECK_UTF8_CONFORM] === true) {
self::check_encoding($data);
}
- if (empty($data)) {
- $response = json_encode($data, $options | JSON_FORCE_OBJECT);
- } else {
- $response = json_encode($data, $options);
- }
- return $response;
+ return json_encode($data, (empty($data) && $forceObjects) ? JSON_FORCE_OBJECT : 0);
}
diff --git a/lib/ArangoDBClient/DocumentHandler.php b/lib/ArangoDBClient/DocumentHandler.php
index 5f18fd91..948dc512 100644
--- a/lib/ArangoDBClient/DocumentHandler.php
+++ b/lib/ArangoDBClient/DocumentHandler.php
@@ -302,6 +302,7 @@ protected function createFromArrayWithContext($data, $options)
*
*
* @return mixed - id of document created
+ * @deprecated use insert with overwriteMode=update or overwriteMode=replace instead
* @since 1.0
*/
public function store(Document $document, $collection = null, array $options = [])
@@ -355,42 +356,13 @@ public function store(Document $document, $collection = null, array $options = [
*/
public function insert($collection, $document, array $options = [])
{
- $headers = [];
- $this->addTransactionHeader($headers, $collection);
-
- $collection = $this->makeCollection($collection);
- $_documentClass = $this->_documentClass;
-
- if (!isset($options[self::OPTION_OVERWRITE_MODE]) &&
- isset($options[self::OPTION_OVERWRITE])) {
- // map "overwrite" to "overwriteMode"
- $options[self::OPTION_OVERWRITE_MODE] = $options[self::OPTION_OVERWRITE] ? 'replace' : 'conflict';
- unset($options[self::OPTION_OVERWRITE]);
- }
-
- $params = $this->includeOptionsInParams(
- $options, [
- 'waitForSync' => $this->getConnectionOption(ConnectionOptions::OPTION_WAIT_SYNC),
- 'returnOld' => (bool) @$options[self::OPTION_RETURN_OLD],
- 'returnNew' => (bool) @$options[self::OPTION_RETURN_NEW],
- ]
- );
-
- if ((isset($options['createCollection']) && $options['createCollection']) ||
- $this->getConnection()->getOption(ConnectionOptions::OPTION_CREATE)) {
- $this->lazyCreateCollection($collection, $params);
- }
-
- $url = UrlHelper::appendParamsUrl(Urls::URL_DOCUMENT . '/' . $collection, $params);
-
if (is_array($document)) {
$data = $document;
} else {
$data = $document->getAllForInsertUpdate();
}
-
- $response = $this->getConnection()->post($url, $this->json_encode_wrapper($data), $headers);
- $json = $response->getJson();
+
+ $response = $this->post($collection, $data, $options);
// This makes sure that if we're in batch mode, it will not go further and choke on the checks below.
// Caution: Instead of a document ID, we are returning the batchpart object
@@ -400,30 +372,32 @@ public function insert($collection, $document, array $options = [])
return $batchPart;
}
- if (@$params[self::OPTION_SILENT]) {
+ if (@$options[self::OPTION_SILENT]) {
// nothing will be returned here
return null;
}
+
+ $json = $response->getJson();
- if ($params[self::OPTION_RETURN_OLD] || $params[self::OPTION_RETURN_NEW]) {
+ if (@$options[self::OPTION_RETURN_OLD] || @$options[self::OPTION_RETURN_NEW]) {
return $json;
}
+ $_documentClass = $this->_documentClass;
if (is_array($document)) {
return $json[$_documentClass::ENTRY_KEY];
}
+ $document->setInternalKey($json[$_documentClass::ENTRY_KEY]);
+ $document->setInternalId($json[$_documentClass::ENTRY_ID]);
+ $document->setRevision($json[$_documentClass::ENTRY_REV]);
+
$location = $response->getLocationHeader();
if (!$location) {
throw new ClientException('Did not find location header in server response');
}
$id = UrlHelper::getDocumentIdFromLocation($location);
-
- $document->setInternalKey($json[$_documentClass::ENTRY_KEY]);
- $document->setInternalId($json[$_documentClass::ENTRY_ID]);
- $document->setRevision($json[$_documentClass::ENTRY_REV]);
-
if ($id !== $document->getId()) {
throw new ClientException('Got an invalid response from the server');
}
@@ -433,10 +407,111 @@ public function insert($collection, $document, array $options = [])
return $document->getId();
}
+
+ /**
+ * insert multiple document into a collection
+ *
+ * This will add the documents to the collection and return the documents' metadata
+ *
+ * This will throw if the documents cannot be saved
+ *
+ * @throws Exception
+ *
+ * @param mixed $collection - collection id as string or number
+ * @param array $documents - the documents to be added, always an array
+ * @param array $options - optional, array of options
+ * Options are :
+ *
'createCollection' - create the collection if it does not yet exist.
+ * '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.
+ * 'keepNull' - can be used to instruct ArangoDB to delete existing attributes on update instead setting their values to null. Defaults to true (keep attributes when set to null). only useful with overwriteMode = update
+ * 'mergeObjects' - if true, updates to object attributes will merge the previous and the new objects. if false, replaces the object attribute with the new value. only useful with overwriteMode = update
+ * 'overwriteMode' - determines overwrite behavior in case a document with the same _key already exists. possible values: 'ignore', 'update', 'replace', 'conflict'.
+ * 'overwrite' - deprecated: if set to true, will turn the insert into a replace operation if a document with the specified key already exists.
+ * 'returnNew' - if set to true, then the newly created document will be returned.
+ * 'returnOld' - if set to true, then the updated/replaced document will be returned - useful only when using overwriteMode = insert/update.
+ * 'silent' - whether or not to return information about the created document (e.g. _key and _rev).
+ *
+ *
+ * @return array - array of document metadata (one entry per document)
+ * @since 3.8
+ */
+ public function insertMany($collection, array $documents, array $options = [])
+ {
+ $data = [];
+ foreach ($documents as $document) {
+ if (is_array($document)) {
+ $data[] = $document;
+ } else {
+ $data[] = $document->getAllForInsertUpdate();
+ }
+ }
+
+ $response = $this->post($collection, $data, $options);
+
+ // This makes sure that if we're in batch mode, it will not go further and choke on the checks below.
+ // Caution: Instead of a document ID, we are returning the batchpart object
+ // The Id of the BatchPart can be retrieved by calling getId() on it.
+ // We're basically returning an object here, in order not to accidentally use the batch part id as the document id
+ if ($batchPart = $response->getBatchPart()) {
+ return $batchPart;
+ }
+
+ return $response->getJson();
+ }
+
+
+ /**
+ * Insert documents into a collection (internal method)
+ *
+ * @throws Exception
+ *
+ * @param string $collection - collection id as string or number
+ * @param mixed $data - document data
+ * @param array $options - array of options
+ *
+ * @internal
+ *
+ * @return HttpResponse - the insert response
+ */
+ protected function post($collection, $data, array $options)
+ {
+ $headers = [];
+ $this->addTransactionHeader($headers, $collection);
+
+ $collection = $this->makeCollection($collection);
+
+ if (!isset($options[self::OPTION_OVERWRITE_MODE]) &&
+ isset($options[self::OPTION_OVERWRITE])) {
+ // map "overwrite" to "overwriteMode"
+ $options[self::OPTION_OVERWRITE_MODE] = $options[self::OPTION_OVERWRITE] ? 'replace' : 'conflict';
+ unset($options[self::OPTION_OVERWRITE]);
+ }
+
+ $params = $this->includeOptionsInParams(
+ $options, [
+ 'waitForSync' => $this->getConnectionOption(ConnectionOptions::OPTION_WAIT_SYNC),
+ 'returnOld' => (bool) @$options[self::OPTION_RETURN_OLD],
+ 'returnNew' => (bool) @$options[self::OPTION_RETURN_NEW],
+ ]
+ );
+
+ if ((isset($options['createCollection']) && $options['createCollection']) ||
+ $this->getConnection()->getOption(ConnectionOptions::OPTION_CREATE)) {
+ $this->lazyCreateCollection($collection, $params);
+ }
+
+ $url = UrlHelper::appendParamsUrl(Urls::URL_DOCUMENT . '/' . $collection, $params);
+
+ return $this->getConnection()->post($url, $this->json_encode_wrapper($data, false), $headers);
+ }
+
+
/**
* Insert a document into a collection
*
* This is an alias for insert().
+ *
+ * * @deprecated use insert instead
*/
public function save($collection, $document, array $options = [])
{
@@ -534,7 +609,6 @@ protected function patch($url, $collection, $documentId, Document $document, arr
$this->addTransactionHeader($headers, $collection);
$collection = $this->makeCollection($collection);
- $_documentClass = $this->_documentClass;
$params = $this->includeOptionsInParams(
$options, [
@@ -568,7 +642,8 @@ protected function patch($url, $collection, $documentId, Document $document, arr
return null;
}
- $json = $result->getJson();
+ $json = $result->getJson();
+ $_documentClass = $this->_documentClass;
$document->setRevision($json[$_documentClass::ENTRY_REV]);
if (@$options[self::OPTION_RETURN_OLD] || @$options[self::OPTION_RETURN_NEW]) {
@@ -668,7 +743,6 @@ protected function put($url, $collection, $documentId, Document $document, array
$this->addTransactionHeader($headers, $collection);
$collection = $this->makeCollection($collection);
- $_documentClass = $this->_documentClass;
$params = $this->includeOptionsInParams(
$options, [
@@ -700,7 +774,8 @@ protected function put($url, $collection, $documentId, Document $document, array
return null;
}
- $json = $result->getJson();
+ $json = $result->getJson();
+ $_documentClass = $this->_documentClass;
$document->setRevision($json[$_documentClass::ENTRY_REV]);
if (@$options[self::OPTION_RETURN_OLD] || @$options[self::OPTION_RETURN_NEW]) {
diff --git a/lib/ArangoDBClient/Handler.php b/lib/ArangoDBClient/Handler.php
index 15ee4488..41e39d03 100644
--- a/lib/ArangoDBClient/Handler.php
+++ b/lib/ArangoDBClient/Handler.php
@@ -72,14 +72,15 @@ protected function getConnectionOption($optionName)
* Return a json encoded string for the array passed.
* This is a convenience function that calls json_encode_wrapper on the connection
*
- * @param array $body - The body to encode into json
+ * @param mixed $data the data to encode
+ * @param bool $forceObjects whether or not to force JSON objects on empty data
*
* @return string - json string of the body that was passed
* @throws \ArangoDBClient\ClientException
*/
- protected function json_encode_wrapper($body)
+ protected function json_encode_wrapper($data, $forceObjects = true)
{
- return $this->getConnection()->json_encode_wrapper($body);
+ return $this->getConnection()->json_encode_wrapper($data, $forceObjects);
}
diff --git a/tests/DocumentBasicTest.php b/tests/DocumentBasicTest.php
index 73f7eb3a..e61cc28e 100644
--- a/tests/DocumentBasicTest.php
+++ b/tests/DocumentBasicTest.php
@@ -73,7 +73,7 @@ public function testInsertSilent()
$document = Document::createFromArray(['_key' => 'me', 'value' => 1]);
$documentHandler = new DocumentHandler($connection);
- $document = $documentHandler->insert($collection->getName(), $document, ['silent' => true ]);
+ $document = $documentHandler->insert($collection->getName(), $document, ['silent' => true]);
static::assertNull($document);
}
@@ -89,7 +89,7 @@ public function testInsertSilentWithError()
$documentHandler = new DocumentHandler($connection);
// insert the document once
- $result = $documentHandler->insert($collection->getName(), $document, ['silent' => true ]);
+ $result = $documentHandler->insert($collection->getName(), $document, ['silent' => true]);
static::assertNull($result);
// and try to insert it again
@@ -111,7 +111,7 @@ public function testInsertReturnNew()
$document = Document::createFromArray(['_key' => 'me', 'value' => 1]);
$documentHandler = new DocumentHandler($connection);
- $document = $documentHandler->insert($collection->getName(), $document, ['returnNew' => true ]);
+ $document = $documentHandler->insert($collection->getName(), $document, ['returnNew' => true]);
static::assertEquals('me', $document['_key']);
static::assertEquals('me', $document['new']['_key']);
@@ -119,6 +119,211 @@ public function testInsertReturnNew()
}
+ /**
+ * Try to insert many documents
+ */
+ public function testInsertMany()
+ {
+ $connection = $this->connection;
+ $collection = $this->collection;
+
+ $documents = [];
+ $documents[] = Document::createFromArray(['_key' => 'test1', 'value' => 1]);
+ $documents[] = Document::createFromArray(['_key' => 'test2', 'value' => 2]);
+ $documents[] = Document::createFromArray(['_key' => 'test3', 'value' => 3]);
+ $documents[] = Document::createFromArray(['_key' => 'test4', 'value' => 4]);
+
+ $documentHandler = new DocumentHandler($connection);
+
+ $result = $documentHandler->insertMany($collection->getName(), $documents);
+ static::assertTrue(is_array($result));
+ static::assertEquals(4, count($result));
+
+ foreach ($result as $i => $doc) {
+ static::assertArrayHasKey('_id', $doc);
+ static::assertArrayHasKey('_key', $doc);
+ static::assertArrayHasKey('_rev', $doc);
+ static::assertEquals('test' . ($i + 1) , $doc['_key']);
+ }
+ }
+
+
+ /**
+ * Try to insert many documents, return new
+ */
+ public function testInsertManyReturnNew()
+ {
+ $connection = $this->connection;
+ $collection = $this->collection;
+
+ $documents = [];
+ $documents[] = Document::createFromArray(['_key' => 'test1', 'value' => 1]);
+ $documents[] = Document::createFromArray(['_key' => 'test2', 'value' => 2]);
+ $documents[] = Document::createFromArray(['_key' => 'test3', 'value' => 3]);
+ $documents[] = Document::createFromArray(['_key' => 'test4', 'value' => 4]);
+
+ $documentHandler = new DocumentHandler($connection);
+
+ $result = $documentHandler->insertMany($collection->getName(), $documents, ['returnNew' => true]);
+ static::assertTrue(is_array($result));
+ static::assertEquals(4, count($result));
+
+ foreach ($result as $i => $doc) {
+ static::assertArrayHasKey('_id', $doc);
+ static::assertArrayHasKey('_key', $doc);
+ static::assertArrayHasKey('_rev', $doc);
+ static::assertEquals('test' . ($i + 1) , $doc['_key']);
+ static::assertArrayHasKey('new', $doc);
+ static::assertEquals('test' . ($i + 1) , $doc['new']['_key']);
+ static::assertEquals($i + 1 , $doc['new']['value']);
+ }
+ }
+
+
+ /**
+ * Try to insert many documents, silent
+ */
+ public function testInsertManySilent()
+ {
+ $connection = $this->connection;
+ $collection = $this->collection;
+
+ $documents = [];
+ $documents[] = Document::createFromArray(['_key' => 'test1', 'value' => 1]);
+ $documents[] = Document::createFromArray(['_key' => 'test2', 'value' => 2]);
+ $documents[] = Document::createFromArray(['_key' => 'test3', 'value' => 3]);
+ $documents[] = Document::createFromArray(['_key' => 'test4', 'value' => 4]);
+
+ $documentHandler = new DocumentHandler($connection);
+
+ $result = $documentHandler->insertMany($collection->getName(), $documents, ['silent' => true]);
+ static::assertTrue(is_array($result));
+ static::assertEquals(0, count($result));
+ }
+
+
+ /**
+ * Try to insert many documents, with errors
+ */
+ public function testInsertManyWithErrors()
+ {
+ $connection = $this->connection;
+ $collection = $this->collection;
+
+ $documents = [];
+ $documents[] = Document::createFromArray(['_key' => 'test1', 'value' => 1]);
+ $documents[] = Document::createFromArray(['_key' => 'test2', 'value' => 2]);
+ $documents[] = Document::createFromArray(['_key' => 'test1', 'value' => 3]);
+ $documents[] = Document::createFromArray(['_key' => 'test2', 'value' => 4]);
+
+ $documentHandler = new DocumentHandler($connection);
+
+ $result = $documentHandler->insertMany($collection->getName(), $documents);
+ static::assertTrue(is_array($result));
+ static::assertEquals(4, count($result));
+
+ foreach ($result as $i => $doc) {
+ if ($i < 2) {
+ static::assertArrayHasKey('_id', $doc);
+ static::assertArrayHasKey('_key', $doc);
+ static::assertArrayHasKey('_rev', $doc);
+ static::assertEquals('test' . ($i + 1) , $doc['_key']);
+ } else {
+ static::assertArrayHasKey('error', $doc);
+ static::assertArrayHasKey('errorNum', $doc);
+ static::assertArrayHasKey('errorMessage', $doc);
+ static::assertTrue($doc['error']);
+ static::assertEquals(1210, $doc['errorNum']);
+ }
+ }
+ }
+
+
+ /**
+ * Try to insert many documents, with errors, silent
+ */
+ public function testInsertManySilentWithErrors()
+ {
+ $connection = $this->connection;
+ $collection = $this->collection;
+
+ $documents = [];
+ $documents[] = Document::createFromArray(['_key' => 'test1', 'value' => 1]);
+ $documents[] = Document::createFromArray(['_key' => 'test2', 'value' => 2]);
+ $documents[] = Document::createFromArray(['_key' => 'test1', 'value' => 3]);
+ $documents[] = Document::createFromArray(['_key' => 'test2', 'value' => 4]);
+
+ $documentHandler = new DocumentHandler($connection);
+
+ $result = $documentHandler->insertMany($collection->getName(), $documents, ['silent' => true]);
+ static::assertTrue(is_array($result));
+ static::assertEquals(2, count($result));
+
+ foreach ($result as $i => $doc) {
+ static::assertArrayHasKey('error', $doc);
+ static::assertArrayHasKey('errorNum', $doc);
+ static::assertArrayHasKey('errorMessage', $doc);
+ static::assertTrue($doc['error']);
+ static::assertEquals(1210, $doc['errorNum']);
+ }
+ }
+
+
+ /**
+ * Try to insert many documents, large request
+ */
+ public function testInsertManyLarge()
+ {
+ $connection = $this->connection;
+ $collection = $this->collection;
+
+ $documents = [];
+ for ($i = 0; $i < 5000; ++$i) {
+ $documents[] = ['_key' => 'test' . $i, 'value' => $i];
+ }
+ $documents[] = ['_key' => 'test0', 'value' => 2];
+
+ $documentHandler = new DocumentHandler($connection);
+
+ $result = $documentHandler->insertMany($collection->getName(), $documents, ['returnNew' => true]);
+ static::assertTrue(is_array($result));
+ static::assertEquals(5001, count($result));
+
+ foreach ($result as $i => $doc) {
+ if ($i < 5000) {
+ static::assertArrayHasKey('_id', $doc);
+ static::assertArrayHasKey('_key', $doc);
+ static::assertArrayHasKey('_rev', $doc);
+ static::assertEquals('test' . $i , $doc['_key']);
+ static::assertArrayHasKey('new', $doc);
+ static::assertEquals('test' . $i , $doc['new']['_key']);
+ static::assertEquals($i , $doc['new']['value']);
+ } else {
+ static::assertArrayHasKey('error', $doc);
+ static::assertArrayHasKey('errorNum', $doc);
+ static::assertArrayHasKey('errorMessage', $doc);
+ static::assertTrue($doc['error']);
+ static::assertEquals(1210, $doc['errorNum']);
+ }
+ }
+ }
+
+ /**
+ * Try to call insertMany with 0 documents
+ */
+ public function testInsertManyEmpty()
+ {
+ $connection = $this->connection;
+ $collection = $this->collection;
+
+ $documentHandler = new DocumentHandler($connection);
+
+ $result = $documentHandler->insertMany($collection->getName(), []);
+ static::assertTrue(is_array($result));
+ static::assertEquals(0, count($result));
+ }
+
+
/**
* Try to create a document and overwrite it, using deprecated overwrite option
*/
From c0d5a44b9a71776bf9701aa8d1d3aa68a975a1e8 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Fri, 23 Apr 2021 16:38:21 +0200
Subject: [PATCH 18/35] added Cursor methods
---
lib/ArangoDBClient/Cursor.php | 21 +++++++++++++++++
tests/StatementTest.php | 44 +++++++++++++++++++++++++++++++++++
2 files changed, 65 insertions(+)
diff --git a/lib/ArangoDBClient/Cursor.php b/lib/ArangoDBClient/Cursor.php
index 7e946ce1..816449cc 100644
--- a/lib/ArangoDBClient/Cursor.php
+++ b/lib/ArangoDBClient/Cursor.php
@@ -833,6 +833,27 @@ public function getFiltered()
{
return $this->getStatValue('filtered');
}
+
+ /**
+ * Return the peak memory usage of the query
+ *
+ * @return int
+ */
+ public function getPeakMemoryUsage()
+ {
+ return $this->getStatValue('peakMemoryUsage');
+ }
+
+
+ /**
+ * Return the execution time of the query
+ *
+ * @return float
+ */
+ public function getExecutionTime()
+ {
+ return $this->getStatValue('executionTime');
+ }
/**
* Return the number of HTTP calls that were made to build the cursor result
diff --git a/tests/StatementTest.php b/tests/StatementTest.php
index 972a3e01..e8ca39b6 100644
--- a/tests/StatementTest.php
+++ b/tests/StatementTest.php
@@ -155,6 +155,50 @@ public function testStatementFailWithMemoryLimit()
static::assertEquals(32, $e->getServerCode());
}
}
+
+
+ /**
+ * Test statistics returned by query
+ */
+ public function testStatisticsPeakMemoryUsage()
+ {
+ $connection = $this->connection;
+ $collection = $this->collection;
+
+ $statement = new Statement($connection, ['_flat' => true]);
+ $statement->setQuery('RETURN (FOR i IN 1..1000 RETURN CONCAT("test", i))');
+ $cursor = $statement->execute();
+
+ $extra = $cursor->getExtra();
+ static::assertEquals([], $extra['warnings']);
+
+ static::assertArrayHasKey('peakMemoryUsage', $extra['stats']);
+ static::assertTrue($extra['stats']['peakMemoryUsage'] > 0);
+ static::assertTrue($cursor->getPeakMemoryUsage() > 0);
+ static::assertEquals($extra['stats']['peakMemoryUsage'], $cursor->getPeakMemoryUsage());
+ }
+
+
+ /**
+ * Test statistics returned by query
+ */
+ public function testStatisticsExecutionTime()
+ {
+ $connection = $this->connection;
+ $collection = $this->collection;
+
+ $statement = new Statement($connection, ['_flat' => true]);
+ $statement->setQuery('RETURN SLEEP(3)');
+ $cursor = $statement->execute();
+
+ $extra = $cursor->getExtra();
+ static::assertEquals([], $extra['warnings']);
+
+ static::assertArrayHasKey('executionTime', $extra['stats']);
+ static::assertTrue($extra['stats']['executionTime'] >= 3.0);
+ static::assertTrue($cursor->getExecutionTime() >= 3.0);
+ static::assertEquals($extra['stats']['executionTime'], $cursor->getExecutionTime());
+ }
/**
From a64211cbceb4f00f53f802904d8ca327e1dd239f Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Fri, 23 Apr 2021 17:03:54 +0200
Subject: [PATCH 19/35] added support for shard count
---
lib/ArangoDBClient/CollectionHandler.php | 13 +++--
lib/ArangoDBClient/Connection.php | 12 ++++-
lib/ArangoDBClient/ConnectionOptions.php | 5 ++
tests/CollectionBasicTest.php | 60 ++++++++++++++++++++++++
4 files changed, 81 insertions(+), 9 deletions(-)
diff --git a/lib/ArangoDBClient/CollectionHandler.php b/lib/ArangoDBClient/CollectionHandler.php
index f0d4c0bd..3ece4e03 100644
--- a/lib/ArangoDBClient/CollectionHandler.php
+++ b/lib/ArangoDBClient/CollectionHandler.php
@@ -394,22 +394,21 @@ public function has($collection)
* @throws Exception
*
* @param mixed $collection - collection id as a string or number
+ * @param bool $details - optional, will provide per-shard counts in a cluster
*
- * @return int - the number of documents in the collection
+ * @return mixed - int if details=false, the number of documents in the collection, array if details=true
*/
- public function count($collection)
+ public function count($collection, $details = false)
{
$headers = [];
$this->addTransactionHeader($headers, $collection);
$collection = $this->makeCollection($collection);
$url = UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collection, self::OPTION_COUNT]);
+ $url = UrlHelper::appendParamsUrl($url, ['details' => $details]);
$response = $this->getConnection()->get($url, $headers);
-
- $data = $response->getJson();
- $count = $data[self::OPTION_COUNT];
-
- return (int) $count;
+ $data = $response->getJson();
+ return $data[self::OPTION_COUNT];
}
diff --git a/lib/ArangoDBClient/Connection.php b/lib/ArangoDBClient/Connection.php
index 265d43dd..ef4db507 100644
--- a/lib/ArangoDBClient/Connection.php
+++ b/lib/ArangoDBClient/Connection.php
@@ -424,9 +424,17 @@ private function updateHttpHeader()
$this->_httpHeader .= sprintf('Host: %s%s', Endpoint::getHost($endpoint), HttpHelper::EOL);
}
- if (isset($this->_options[ConnectionOptions::OPTION_AUTH_TYPE], $this->_options[ConnectionOptions::OPTION_AUTH_USER])) {
+ if (isset($this->_options[ConnectionOptions::OPTION_AUTH_JWT])) {
+ // JWT, used as is
+ $this->_httpHeader .= sprintf(
+ 'Authorization: Bearer %s%s',
+ $this->_options[ConnectionOptions::OPTION_AUTH_JWT],
+ HttpHelper::EOL
+ );
+ } else if (isset($this->_options[ConnectionOptions::OPTION_AUTH_TYPE], $this->_options[ConnectionOptions::OPTION_AUTH_USER])) {
+ // create a JWT for a given user and server's JWT secret
if ($this->_options[ConnectionOptions::OPTION_AUTH_TYPE] == 'Bearer') {
- // JWT
+ // JWT secret
$base = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);
$base64UrlHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($base));
$payload = json_encode([
diff --git a/lib/ArangoDBClient/ConnectionOptions.php b/lib/ArangoDBClient/ConnectionOptions.php
index 679a72d6..511225dd 100644
--- a/lib/ArangoDBClient/ConnectionOptions.php
+++ b/lib/ArangoDBClient/ConnectionOptions.php
@@ -169,6 +169,11 @@ class ConnectionOptions implements \ArrayAccess
* Wait for sync index constant
*/
const OPTION_IS_SYSTEM = 'isSystem';
+
+ /**
+ * Authentication JWT
+ */
+ const OPTION_AUTH_JWT = 'Jwt';
/**
* Authentication user name
diff --git a/tests/CollectionBasicTest.php b/tests/CollectionBasicTest.php
index 3f9851ee..fb254e94 100644
--- a/tests/CollectionBasicTest.php
+++ b/tests/CollectionBasicTest.php
@@ -1287,6 +1287,66 @@ public function testHasCollectionReturnsTrueIfCollectionExists()
}
+ /**
+ * count
+ */
+ public function testCollectionCountDetailed()
+ {
+ 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);
+
+ $name = 'ArangoDB_PHP_TestSuite_TestCollection_01' . '_' . static::$testsTimestamp;
+
+ try {
+ $collectionHandler->drop($name);
+ } catch (Exception $e) {
+ //Silence the exception
+ }
+
+ $collection = Collection::createFromArray(['name' => $name, 'numberOfShards' => 5]);
+ $collectionHandler->create($collection);
+
+ $count = $collectionHandler->count($collection, false);
+ static::assertEquals(0, $count);
+
+ $count = $collectionHandler->count($collection, true);
+ static::assertTrue(is_array($count));
+ static::assertEquals(5, count($count));
+
+ foreach ($count as $shard => $value) {
+ static::assertEquals('s', $shard[0]);
+ static::assertEquals(0, $value);
+ }
+
+ // fill with data
+ $statement = new Statement($connection, []);
+ $statement->setQuery('FOR i IN 1..1000 INSERT {} IN ' . $collection->getName());
+ $cursor = $statement->execute();
+
+ $count = $collectionHandler->count($collection, false);
+ static::assertEquals(1000, $count);
+
+ $count = $collectionHandler->count($collection, true);
+ static::assertTrue(is_array($count));
+ static::assertEquals(5, count($count));
+
+ $sum = 0;
+ foreach ($count as $shard => $value) {
+ static::assertEquals('s', $shard[0]);
+ static::assertTrue($value > 0);
+ $sum += $value;
+ }
+
+ static::assertEquals(1000, $sum);
+ }
+
+
/**
* get shards
*/
From 0da7a02ac06cd811bfe4f7f85126ba5d0ac21dbf Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Fri, 23 Apr 2021 17:04:13 +0200
Subject: [PATCH 20/35] updated CHANGELOG
---
CHANGELOG.md | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f3fed620..50d40a8a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,12 +16,23 @@ $connectionOptions = [
...
ArangoDBClient\ConnectionOptions::OPTION_AUTH_TYPE => 'Bearer', // authentication via JWT!
ArangoDBClient\ConnectionOptions::OPTION_AUTH_USER => 'root', // user name
- ArangoDBClient\ConnectionOptions::OPTION_AUTH_PASSWD => 'jwt-secret-value', // server's JWT secret value,
+ ArangoDBClient\ConnectionOptions::OPTION_AUTH_PASSWD => 'jwt-secret-value', // server's JWT secret value
];
```
Note that the server's JWT _secret_, not a generated JWT, must go into the `OPTION_AUTH_PASSWD`
ConnectionOption.
+In order to use an already generated JWT without any username, set the ConnectionOptions
+as follows:
+```
+$connectionOptions = [
+ ArangoDBClient\ConnectionOptions::OPTION_DATABASE => '_system', // database name
+ ArangoDBClient\ConnectionOptions::OPTION_ENDPOINT => 'tcp://127.0.0.1:8529', // endpoint to connect to
+ ...
+ ArangoDBClient\ConnectionOptions::OPTION_AUTH_JWT => '.......', // full JWT needs to go here
+ ];
+```
+
The driver now supports the following options for document CRUD operations:
- "overwriteMode"
- "silent"
From b3bef925c7c98aecf85d2faacf65edd2f2fcc0c0 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Fri, 23 Apr 2021 17:20:03 +0200
Subject: [PATCH 21/35] adjust cluster tests
---
tests/DocumentBasicTest.php | 41 +++++++++++++++++++++++++++++--------
1 file changed, 33 insertions(+), 8 deletions(-)
diff --git a/tests/DocumentBasicTest.php b/tests/DocumentBasicTest.php
index e61cc28e..ea87a47f 100644
--- a/tests/DocumentBasicTest.php
+++ b/tests/DocumentBasicTest.php
@@ -198,7 +198,12 @@ public function testInsertManySilent()
$result = $documentHandler->insertMany($collection->getName(), $documents, ['silent' => true]);
static::assertTrue(is_array($result));
- static::assertEquals(0, count($result));
+
+ if (isCluster($this->connection)) {
+ static::assertEquals(4, count($result));
+ } else {
+ static::assertEquals(0, count($result));
+ }
}
@@ -257,14 +262,34 @@ public function testInsertManySilentWithErrors()
$result = $documentHandler->insertMany($collection->getName(), $documents, ['silent' => true]);
static::assertTrue(is_array($result));
- static::assertEquals(2, count($result));
+
+ if (isCluster($this->connection)) {
+ static::assertEquals(4, count($result));
+
+ foreach ($result as $i => $doc) {
+ if ($i < 2) {
+ static::assertArrayHasKey('_id', $doc);
+ static::assertArrayHasKey('_key', $doc);
+ static::assertArrayHasKey('_rev', $doc);
+ static::assertEquals('test' . ($i + 1) , $doc['_key']);
+ } else {
+ static::assertArrayHasKey('error', $doc);
+ static::assertArrayHasKey('errorNum', $doc);
+ static::assertArrayHasKey('errorMessage', $doc);
+ static::assertTrue($doc['error']);
+ static::assertEquals(1210, $doc['errorNum']);
+ }
+ }
+ } else {
+ static::assertEquals(2, count($result));
- foreach ($result as $i => $doc) {
- static::assertArrayHasKey('error', $doc);
- static::assertArrayHasKey('errorNum', $doc);
- static::assertArrayHasKey('errorMessage', $doc);
- static::assertTrue($doc['error']);
- static::assertEquals(1210, $doc['errorNum']);
+ foreach ($result as $i => $doc) {
+ static::assertArrayHasKey('error', $doc);
+ static::assertArrayHasKey('errorNum', $doc);
+ static::assertArrayHasKey('errorMessage', $doc);
+ static::assertTrue($doc['error']);
+ static::assertEquals(1210, $doc['errorNum']);
+ }
}
}
From 1bf5198ebb2d5c61258f47d426579d8187bd7db7 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Wed, 28 Apr 2021 09:08:12 +0200
Subject: [PATCH 22/35] added query profiling
---
lib/ArangoDBClient/Statement.php | 41 +++++++++++++++++++++++++++++++-
tests/StatementTest.php | 29 ++++++++++++++++++++++
2 files changed, 69 insertions(+), 1 deletion(-)
diff --git a/lib/ArangoDBClient/Statement.php b/lib/ArangoDBClient/Statement.php
index 52a9dc7e..b60fb7a3 100644
--- a/lib/ArangoDBClient/Statement.php
+++ b/lib/ArangoDBClient/Statement.php
@@ -149,6 +149,13 @@ class Statement
* @var bool
*/
private $_failOnWarning = false;
+
+ /**
+ * Whether or not the query should return profiling information
+ *
+ * @var bool
+ */
+ private $_profile = false;
/**
* Approximate memory limit value (in bytes) that a query can use on the server-side
@@ -202,6 +209,11 @@ class Statement
*/
const ENTRY_FAIL_ON_WARNING = 'failOnWarning';
+ /**
+ * Profile flag
+ */
+ const ENTRY_PROFILE = 'profile';
+
/**
* Memory limit threshold for query
*/
@@ -309,6 +321,10 @@ public function __construct(Connection $connection, array $data)
$this->_failOnWarning = (bool) $data[self::ENTRY_FAIL_ON_WARNING];
}
+ if (isset($data[self::ENTRY_PROFILE])) {
+ $this->_profile = (bool) $data[self::ENTRY_PROFILE];
+ }
+
if (isset($data[self::ENTRY_MEMORY_LIMIT])) {
$this->_memoryLimit = (int) $data[self::ENTRY_MEMORY_LIMIT];
}
@@ -658,6 +674,28 @@ public function getFailOnWarning()
return $this->_failOnWarning;
}
+ /**
+ * Set whether or not query profiling should be enabled
+ *
+ * @param bool $value - value for profiling
+ *
+ * @return void
+ */
+ public function setProfile($value = true)
+ {
+ $this->_profile = (bool) $value;
+ }
+
+ /**
+ * Get the configured value for profiling
+ *
+ * @return bool - current value of profiling option
+ */
+ public function getProfiling()
+ {
+ return $this->_profile;
+ }
+
/**
* Set the approximate memory limit threshold to be used by the query on the server-side
* (a value of 0 or less will mean the memory is not limited)
@@ -729,7 +767,8 @@ private function buildData()
self::ENTRY_COUNT => $this->_doCount,
'options' => [
self::FULL_COUNT => $this->_fullCount,
- self::ENTRY_FAIL_ON_WARNING => $this->_failOnWarning
+ self::ENTRY_FAIL_ON_WARNING => $this->_failOnWarning,
+ self::ENTRY_PROFILE => $this->_profile
]
];
diff --git a/tests/StatementTest.php b/tests/StatementTest.php
index e8ca39b6..7961fea7 100644
--- a/tests/StatementTest.php
+++ b/tests/StatementTest.php
@@ -609,6 +609,35 @@ public function testMaxRuntime()
static::assertTrue($excepted);
}
+ public function testProfiling()
+ {
+ $connection = $this->connection;
+
+ $statement = new Statement(
+ $connection, [
+ 'query' => 'FOR i IN 1..10000 RETURN CONCAT("testisiteisiitit", i)',
+ '_flat' => true
+ ]
+ );
+ static::assertFalse($statement->getProfiling());
+
+ $statement = new Statement(
+ $connection, [
+ 'query' => 'FOR i IN 1..10000 RETURN CONCAT("testisiteisiitit", i)',
+ 'profile' => true,
+ '_flat' => true
+ ]
+ );
+
+ static::assertTrue($statement->getProfiling());
+
+ $cursor = $statement->execute();
+ $result = $cursor->getExtra();
+ static::assertArrayHasKey('profile', $result);
+ static::assertTrue(is_array($result['profile']));
+ static::assertArrayHasKey('executing', $result['profile']);
+ }
+
public function testStatementStreaming()
{
$connection = $this->connection;
From 18a524d6eb5b323b71ea5c9c2adc217cc85fc7b4 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Wed, 28 Apr 2021 09:11:19 +0200
Subject: [PATCH 23/35] updated CHANGELOG
---
CHANGELOG.md | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50d40a8a..326b3934 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -36,13 +36,21 @@ $connectionOptions = [
The driver now supports the following options for document CRUD operations:
- "overwriteMode"
- "silent"
-- "keepNull", "mergeObjects" (for insert API if overwriteMode=update)
+- "keepNull", "mergeObjects" (for insert/insertMany API if overwriteMode=update)
Extended maximum valid length for collection names to 256, up from 64 before. This follows a
change in the ArangoDB server.
+Additional options for AQL queries are now supported via `Statement`:
+- memoryLimit
+- profile
+- maxRuntime
+
Indexes can now be created with "deduplicate" and "estimates" attribute set/not set.
+The count functionality in `CollectionHandler::count()` now supports returned the detailed
+shard count by passing an extra boolean parameter.
+
The engine stats API is now supported via `AdminHandler::getEngineStats()`.
The metrics API is now supported via `AdminHandler::getMetrics()`.
From a96f5283195bf837e4aa3432324db66367dae2d4 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Wed, 28 Apr 2021 13:12:17 +0200
Subject: [PATCH 24/35] update examples
---
UNITTESTS.md | 7 -------
examples/aql-query.php | 19 ++++++++++++++-----
examples/batch.php | 2 --
examples/init.php | 17 +++++++++++++----
4 files changed, 27 insertions(+), 18 deletions(-)
delete mode 100644 UNITTESTS.md
delete mode 100644 examples/batch.php
diff --git a/UNITTESTS.md b/UNITTESTS.md
deleted file mode 100644
index d999341b..00000000
--- a/UNITTESTS.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# PHPUnit Tests for ArangoDB-PHP
-
-To run the unit tests, cd into the `tests` folder and run:
-
-```
-phpunit --testsuite ArangoDB-PHP
-```
diff --git a/examples/aql-query.php b/examples/aql-query.php
index 1396eae3..b0b96ea2 100644
--- a/examples/aql-query.php
+++ b/examples/aql-query.php
@@ -43,17 +43,26 @@
foreach ($statements as $query => $bindVars) {
$statement = new Statement($connection, [
- 'query' => $query,
- 'count' => true,
- 'batchSize' => 1000,
- 'bindVars' => $bindVars,
- 'sanitize' => true,
+ 'query' => $query,
+ 'count' => true,
+ 'batchSize' => 1000,
+ 'bindVars' => $bindVars,
+ 'profile' => false, // turn this on for query profiling
+ 'memoryLimit' => 16 * 1024 * 1024, // optional server-side memory limit for query
+ 'maxRuntime' => 10.0, // optional server-side runtime for query
+ 'sanitize' => true,
+ '_flat' => false, // set this to true when the query result is not an array of documents
+
]
);
echo 'RUNNING STATEMENT ' . $statement . PHP_EOL;
$cursor = $statement->execute();
+
+ // get information about query runtime, peak memory usage etc.
+ // var_dump($cursor->getExtra());
+
foreach ($cursor->getAll() as $doc) {
echo '- RETURN VALUE: ' . json_encode($doc) . PHP_EOL;
}
diff --git a/examples/batch.php b/examples/batch.php
deleted file mode 100644
index c38e4af6..00000000
--- a/examples/batch.php
+++ /dev/null
@@ -1,2 +0,0 @@
- 'unix:///tmp/arangodb.sock', // UNIX domain socket
ConnectionOptions::OPTION_CONNECTION => 'Keep-Alive', // can use either 'Close' (one-time connections) or 'Keep-Alive' (re-used connections)
- ConnectionOptions::OPTION_AUTH_TYPE => 'Basic', // use basic authorization
- // authentication parameters (note: must also start server with option `--server.disable-authentication false`)
- ConnectionOptions::OPTION_AUTH_USER => 'root', // user for basic authorization
- ConnectionOptions::OPTION_AUTH_PASSWD => '', // password for basic authorization
+ // authentication parameters
+ ConnectionOptions::OPTION_AUTH_TYPE => 'Basic', // use HTTP Basic authorization
+ ConnectionOptions::OPTION_AUTH_USER => 'root', // user for Basic authorization
+ ConnectionOptions::OPTION_AUTH_PASSWD => '', // password for Basic authorization
+
+ // in order to not send passwords, it is possible to make the driver generate a JWT for an existing user.
+ // this requires knowledge of the server's JWT secret key, however:
+ // ConnectionOptions::OPTION_AUTH_TYPE => 'Bearer', // use HTTP Bearer authorization
+ // ConnectionOptions::OPTION_AUTH_USER => 'root', // user name
+ // ConnectionOptions::OPTION_AUTH_PASSWD => '', // server's JWT secret needs to go in here
+
+ // in order to use an externally generated JWT, there is no need to specify user and passwd, but just the JWT value:
+ // ConnectionOptions::OPTION_AUTH_JWT => '', // use an externally generated JWT for authorization
ConnectionOptions::OPTION_TIMEOUT => 30, // timeout in seconds
// ConnectionOptions::OPTION_TRACE => $traceFunc, // tracer function, can be used for debugging
From 8199e6cdd86a66421fcc0dae43d9113722ab9fe9 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Wed, 28 Apr 2021 09:14:29 +0200
Subject: [PATCH 25/35] add more PHP versions
---
.travis.yml | 3 +++
tests/travis/setup_arangodb.sh | 15 +++++++++++++++
2 files changed, 18 insertions(+)
diff --git a/.travis.yml b/.travis.yml
index eede20e6..6e985cd2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,6 +10,9 @@ matrix:
- php: '5.6'
- php: '7.0'
- php: '7.1'
+ - php: '7.2'
+ - php: '7.3'
+ - php: '7.4'
allow_failures:
- php: hhvm
diff --git a/tests/travis/setup_arangodb.sh b/tests/travis/setup_arangodb.sh
index 48c68914..e0983f2c 100644
--- a/tests/travis/setup_arangodb.sh
+++ b/tests/travis/setup_arangodb.sh
@@ -17,6 +17,21 @@ wget "https://phar.phpunit.de/phpunit-6.0.phar"
mv phpunit-6.0.phar ./phpunit
fi
+if [[ "$TRAVIS_PHP_VERSION" == "7.2" ]] ; then
+wget "https://phar.phpunit.de/phpunit-6.0.phar"
+mv phpunit-6.0.phar ./phpunit
+fi
+
+if [[ "$TRAVIS_PHP_VERSION" == "7.3" ]] ; then
+wget "https://phar.phpunit.de/phpunit-6.0.phar"
+mv phpunit-6.0.phar ./phpunit
+fi
+
+if [[ "$TRAVIS_PHP_VERSION" == "7.4" ]] ; then
+wget "https://phar.phpunit.de/phpunit-6.0.phar"
+mv phpunit-6.0.phar ./phpunit
+fi
+
if [[ "$TRAVIS_PHP_VERSION" == "hhvm" ]] ; then
wget "https://phar.phpunit.de/phpunit-6.0.phar"
mv phpunit-6.0.phar ./phpunit
From 5bd8c4aa09868655d20d1688e90de08f4f826d8d Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Wed, 28 Apr 2021 09:16:48 +0200
Subject: [PATCH 26/35] add 3.8 badge
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 51f10fca..2566922b 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@ The official ArangoDB PHP Driver.
3.5: [](https://travis-ci.org/arangodb/arangodb-php)
3.6: [](https://travis-ci.org/arangodb/arangodb-php)
3.7: [](https://travis-ci.org/arangodb/arangodb-php)
+3.8: [](https://travis-ci.org/arangodb/arangodb-php)
devel: [](https://travis-ci.org/arangodb/arangodb-php)
- [Getting Started](https://www.arangodb.com/docs/stable/drivers/php-getting-started.html)
From 376999a3645d385814a8ac2fea95fb6a13141fdd Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Wed, 9 Jun 2021 10:01:28 +0200
Subject: [PATCH 27/35] add tests and disable them until 3.8.1
---
tests/AnalyzerTest.php | 63 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)
diff --git a/tests/AnalyzerTest.php b/tests/AnalyzerTest.php
index f7bec85b..5e2d7ecb 100644
--- a/tests/AnalyzerTest.php
+++ b/tests/AnalyzerTest.php
@@ -85,6 +85,69 @@ public function testCreateTextAnalyzerFail()
}
static::assertEquals(400, $exception->getCode());
}
+
+ /**
+ * Test creation of stopwords analyzer
+ */
+ /* disabled until 3.8.1
+ public function testCreateStopwordsAnalyzer()
+ {
+ $analyzer = new Analyzer('Analyzer1' . '_' . static::$testsTimestamp, 'stopwords', [ "stopwords" => ["foo", "bar", "baz", "dead"] ]);
+ $result = $this->analyzerHandler->create($analyzer);
+ static::assertEquals('Analyzer1' . '_' . static::$testsTimestamp, $result['name']);
+ static::assertEquals('stopwords', $result['type']);
+ static::assertEquals([ "stopwords" => ["foo", "bar", "baz", "dead"] ],$analyzer->getProperties());
+ static::assertEquals([], $analyzer->getFeatures());
+ }
+ */
+
+ /**
+ * Test creation of delimiter analyzer
+ */
+ public function testCreateDelimiterAnalyzer()
+ {
+ $analyzer = new Analyzer('Analyzer1' . '_' . static::$testsTimestamp, 'delimiter', [ "delimiter" => " " ]);
+ $result = $this->analyzerHandler->create($analyzer);
+ static::assertEquals('Analyzer1' . '_' . static::$testsTimestamp, $result['name']);
+ static::assertEquals('delimiter', $result['type']);
+ static::assertEquals([ "delimiter" => " " ],$analyzer->getProperties());
+ static::assertEquals([], $analyzer->getFeatures());
+ }
+
+ /**
+ * Test creation of norm analyzer
+ */
+ public function testCreateNormAnalyzer()
+ {
+ $analyzer = new Analyzer('Analyzer1' . '_' . static::$testsTimestamp, 'norm', [ "locale" => "en.UTF-8", "accent" => false, "case" => "lower" ]);
+ $result = $this->analyzerHandler->create($analyzer);
+ static::assertEquals('Analyzer1' . '_' . static::$testsTimestamp, $result['name']);
+ static::assertEquals('norm', $result['type']);
+ static::assertEquals([ "locale" => "en.UTF-8", "accent" => false, "case" => "lower" ],$analyzer->getProperties());
+ static::assertEquals([], $analyzer->getFeatures());
+ }
+
+ /**
+ * Test creation of pipeline analyzer
+ */
+ /* disabled until 3.8.1
+ public function testCreatePipelineAnalyzer()
+ {
+ $data = [ "pipeline" => [
+ [ "type" => "delimiter", "properties" => [ "delimiter" => " " ] ],
+ [ "type" => "norm", "properties" => [ "locale" => "en.UTF-8", "accent" => false, "case" => "lower" ] ],
+ [ "type" => "stopwords", "properties" => [ "stopwords" => ["foo", "bar", "baz", "dead"] ] ]
+ ] ];
+
+ $analyzer = new Analyzer('Analyzer1' . '_' . static::$testsTimestamp, 'pipeline', $data);
+ $result = $this->analyzerHandler->create($analyzer);
+
+ static::assertEquals('Analyzer1' . '_' . static::$testsTimestamp, $result['name']);
+ static::assertEquals('pipeline', $result['type']);
+ static::assertEquals($data, $analyzer->getProperties());
+ static::assertEquals([], $analyzer->getFeatures());
+ }
+ */
/**
* Test getting an analyzer
From 1dfe2eddeddc4058e758392b1085686fd2fb8c2e Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Wed, 9 Jun 2021 21:18:37 +0200
Subject: [PATCH 28/35] issue #281: split OPTION_TIMEOUT into separate options
OPTION_TIMEOUT is now superseded by the more specialized options
- OPTION_CONNECT_TIMEOUT
- OPTION_REQUEST_TIMEOUT
The existing OPTION_TIMEOUT value can still be used as before. When
used, it will automatically clobber values set in OPTION_CONNECT_TIMEOUT
and OPTION_REQUEST_TIMEOUT.
Using one of the more specialized options will remove OPTION_TIMEOUT
from the connection's option.
---
lib/ArangoDBClient/Connection.php | 3 +-
lib/ArangoDBClient/ConnectionOptions.php | 25 ++++-
lib/ArangoDBClient/DefaultValues.php | 11 ++
lib/ArangoDBClient/HttpHelper.php | 4 +-
tests/ConnectionTest.php | 132 ++++++++++++++++++++++-
tests/QueryTest.php | 8 +-
6 files changed, 173 insertions(+), 10 deletions(-)
diff --git a/lib/ArangoDBClient/Connection.php b/lib/ArangoDBClient/Connection.php
index ef4db507..ebbc89fc 100644
--- a/lib/ArangoDBClient/Connection.php
+++ b/lib/ArangoDBClient/Connection.php
@@ -151,7 +151,8 @@ public function setOption($name, $value)
$this->_options[$name] = $value;
// special handling for several options
- if ($name === ConnectionOptions::OPTION_TIMEOUT) {
+ if ($name === ConnectionOptions::OPTION_TIMEOUT ||
+ $name === ConnectionOptions::OPTION_REQUEST_TIMEOUT) {
// set the timeout option: patch the stream of an existing connection
if (is_resource($this->_handle)) {
stream_set_timeout($this->_handle, $value);
diff --git a/lib/ArangoDBClient/ConnectionOptions.php b/lib/ArangoDBClient/ConnectionOptions.php
index 511225dd..a1bd61ae 100644
--- a/lib/ArangoDBClient/ConnectionOptions.php
+++ b/lib/ArangoDBClient/ConnectionOptions.php
@@ -66,9 +66,20 @@ class ConnectionOptions implements \ArrayAccess
/**
* Timeout value index constant
+ * @deprecated superseded by OPTION_CONNECT_TIMEOUT and OPTION_REQUEST_TIMEOUT
*/
const OPTION_TIMEOUT = 'timeout';
+ /**
+ * Connect timeout value index constant
+ */
+ const OPTION_CONNECT_TIMEOUT = 'connectTimeout';
+
+ /**
+ * Request timeout value index constant
+ */
+ const OPTION_REQUEST_TIMEOUT = 'requestTimeout';
+
/**
* Number of servers tried in case of failover
* if set to 0, then an unlimited amount of servers will be tried
@@ -293,6 +304,11 @@ public function getAll()
public function offsetSet($offset, $value)
{
$this->_values[$offset] = $value;
+ if ($offset === self::OPTION_CONNECT_TIMEOUT || $offset === self::OPTION_REQUEST_TIMEOUT) {
+ // special handling for OPTION_TIMEOUT: it will be removed once
+ // a more specialized option is used
+ unset($this->_values[self::OPTION_TIMEOUT]);
+ }
$this->validate();
}
@@ -439,7 +455,8 @@ private static function getDefaults()
self::OPTION_PORT => DefaultValues::DEFAULT_PORT,
self::OPTION_FAILOVER_TRIES => DefaultValues::DEFAULT_FAILOVER_TRIES,
self::OPTION_FAILOVER_TIMEOUT => DefaultValues::DEFAULT_FAILOVER_TIMEOUT,
- self::OPTION_TIMEOUT => DefaultValues::DEFAULT_TIMEOUT,
+ self::OPTION_CONNECT_TIMEOUT => DefaultValues::DEFAULT_CONNECT_TIMEOUT,
+ self::OPTION_REQUEST_TIMEOUT => DefaultValues::DEFAULT_REQUEST_TIMEOUT,
self::OPTION_MEMCACHED_PERSISTENT_ID => 'arangodb-php-pool',
self::OPTION_MEMCACHED_OPTIONS => [ ],
self::OPTION_MEMCACHED_ENDPOINTS_KEY => 'arangodb-php-endpoints',
@@ -575,6 +592,12 @@ private function validate()
)
);
}
+
+ if (isset($this->_values[self::OPTION_TIMEOUT])) {
+ // propagate values from OPTION_TIMOEUT into OPTION_CONNECT_TIMEOUT and OPTION_REQUEST_TIMEOUT
+ $this->_values[self::OPTION_CONNECT_TIMEOUT] = (float) $this->_values[self::OPTION_TIMEOUT];
+ $this->_values[self::OPTION_REQUEST_TIMEOUT] = (float) $this->_values[self::OPTION_TIMEOUT];
+ }
UpdatePolicy::validate($this->_values[self::OPTION_UPDATE_POLICY]);
UpdatePolicy::validate($this->_values[self::OPTION_REPLACE_POLICY]);
diff --git a/lib/ArangoDBClient/DefaultValues.php b/lib/ArangoDBClient/DefaultValues.php
index 84b34dde..d9d0a798 100644
--- a/lib/ArangoDBClient/DefaultValues.php
+++ b/lib/ArangoDBClient/DefaultValues.php
@@ -27,9 +27,20 @@ abstract class DefaultValues
/**
* Default timeout value (used if no timeout value specified)
+ * @deprecated superseded by DEFAULT_CONNECT_TIMEOUT and DEFAULT_REQUEST_TIMEOUT
*/
const DEFAULT_TIMEOUT = 30;
+ /**
+ * Default connect timeout value (used if no timeout value specified)
+ */
+ const DEFAULT_CONNECT_TIMEOUT = 30;
+
+ /**
+ * Default request timeout value (used if no timeout value specified)
+ */
+ const DEFAULT_REQUEST_TIMEOUT = 30;
+
/**
* Default number of failover servers to try (used in case there is an automatic failover)
* if set to 0, then an unlimited amount of servers will be tried
diff --git a/lib/ArangoDBClient/HttpHelper.php b/lib/ArangoDBClient/HttpHelper.php
index 7395e5fe..8ef9e67c 100644
--- a/lib/ArangoDBClient/HttpHelper.php
+++ b/lib/ArangoDBClient/HttpHelper.php
@@ -104,7 +104,7 @@ public static function createConnection(ConnectionOptions $options)
Endpoint::normalize($endpoint),
$errNo,
$message,
- $options[ConnectionOptions::OPTION_TIMEOUT],
+ $options[ConnectionOptions::OPTION_CONNECT_TIMEOUT],
STREAM_CLIENT_CONNECT,
$context
);
@@ -116,7 +116,7 @@ public static function createConnection(ConnectionOptions $options)
);
}
- stream_set_timeout($fp, $options[ConnectionOptions::OPTION_TIMEOUT]);
+ stream_set_timeout($fp, $options[ConnectionOptions::OPTION_REQUEST_TIMEOUT]);
return $fp;
}
diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php
index 64abc0b8..ebce4f6a 100644
--- a/tests/ConnectionTest.php
+++ b/tests/ConnectionTest.php
@@ -152,7 +152,7 @@ public function testGetStatus()
$response = $connection->get('/_admin/statistics');
static::assertEquals(200, $response->getHttpCode(), 'Did not return http code 200');
}
-
+
/**
* Test get options
*/
@@ -208,6 +208,78 @@ public function testSetOptions()
$value = $connection->getOption(ConnectionOptions::OPTION_RECONNECT);
static::assertFalse($value);
}
+
+ /**
+ * Test timeout options handling
+ */
+ public function testTimeoutOptions()
+ {
+ $connection = getConnection();
+
+ $oldTimeout = $connection->getOption(ConnectionOptions::OPTION_TIMEOUT);
+ $oldConnectTimeout = $connection->getOption(ConnectionOptions::OPTION_CONNECT_TIMEOUT);
+ $oldRequestTimeout = $connection->getOption(ConnectionOptions::OPTION_REQUEST_TIMEOUT);
+
+ static::assertEquals($oldTimeout, $oldConnectTimeout);
+ static::assertEquals($oldTimeout, $oldRequestTimeout);
+
+ $connection->setOption(ConnectionOptions::OPTION_TIMEOUT, 12);
+ $newTimeout = $connection->getOption(ConnectionOptions::OPTION_TIMEOUT);
+ $newConnectTimeout = $connection->getOption(ConnectionOptions::OPTION_CONNECT_TIMEOUT);
+ $newRequestTimeout = $connection->getOption(ConnectionOptions::OPTION_REQUEST_TIMEOUT);
+
+ static::assertEquals(12, $newTimeout);
+ static::assertEquals(12, $newConnectTimeout);
+ static::assertEquals(12, $newRequestTimeout);
+
+ $connection->setOption(ConnectionOptions::OPTION_TIMEOUT, 42);
+ $newTimeout = $connection->getOption(ConnectionOptions::OPTION_TIMEOUT);
+ $newConnectTimeout = $connection->getOption(ConnectionOptions::OPTION_CONNECT_TIMEOUT);
+ $newRequestTimeout = $connection->getOption(ConnectionOptions::OPTION_REQUEST_TIMEOUT);
+
+ static::assertEquals(42, $newTimeout);
+ static::assertEquals(42, $newConnectTimeout);
+ static::assertEquals(42, $newRequestTimeout);
+
+ $connection->setOption(ConnectionOptions::OPTION_CONNECT_TIMEOUT, 1.5);
+ try {
+ $connection->getOption(ConnectionOptions::OPTION_TIMEOUT);
+ static::assertFalse(true);
+ } catch (\Exception $e) {
+ // OPTION_TIMEOUT is gone once OPTION_CONNECT_TIMEOUT is used
+ }
+
+ $newConnectTimeout = $connection->getOption(ConnectionOptions::OPTION_CONNECT_TIMEOUT);
+ $newRequestTimeout = $connection->getOption(ConnectionOptions::OPTION_REQUEST_TIMEOUT);
+
+ static::assertEquals(1.5, $newConnectTimeout);
+ static::assertEquals(42, $newRequestTimeout);
+
+ $connection->setOption(ConnectionOptions::OPTION_REQUEST_TIMEOUT, 24.5);
+ $newConnectTimeout = $connection->getOption(ConnectionOptions::OPTION_CONNECT_TIMEOUT);
+ $newRequestTimeout = $connection->getOption(ConnectionOptions::OPTION_REQUEST_TIMEOUT);
+
+ try {
+ $connection->getOption(ConnectionOptions::OPTION_TIMEOUT);
+ static::assertFalse(true);
+ } catch (\Exception $e) {
+ // OPTION_TIMEOUT is gone once OPTION_REQUEST_TIMEOUT is used
+ }
+
+ static::assertEquals(1.5, $newConnectTimeout);
+ static::assertEquals(24.5, $newRequestTimeout);
+
+
+ $connection->setOption(ConnectionOptions::OPTION_TIMEOUT, 8);
+ $newTimeout = $connection->getOption(ConnectionOptions::OPTION_TIMEOUT);
+ $newConnectTimeout = $connection->getOption(ConnectionOptions::OPTION_CONNECT_TIMEOUT);
+ $newRequestTimeout = $connection->getOption(ConnectionOptions::OPTION_REQUEST_TIMEOUT);
+
+ static::assertEquals(8, $newTimeout);
+ static::assertEquals(8, $newConnectTimeout);
+ static::assertEquals(8, $newRequestTimeout);
+ }
+
/**
* Test set invalid options
@@ -340,7 +412,7 @@ public function testSetTimeoutException()
throw $exception;
}
}
-
+
/**
* Test timeout, no exception
*/
@@ -356,6 +428,62 @@ public function testSetTimeout()
$cursor = $statement->execute();
static::assertCount(1, $cursor->getAll());
}
+
+ /**
+ * Test connect timeout, no exception
+ */
+ public function testSetConnectTimeout()
+ {
+ $connection = getConnection();
+ $connection->setOption(ConnectionOptions::OPTION_CONNECT_TIMEOUT, 5);
+ $query = 'RETURN SLEEP(1)';
+
+ $statement = new Statement($connection, ['query' => $query]);
+
+ // should work
+ $cursor = $statement->execute();
+ static::assertCount(1, $cursor->getAll());
+ }
+
+ /**
+ * Test request timeout exception
+ *
+ * @expectedException \ArangoDBClient\ClientException
+ */
+ public function testSetRequestTimeoutException()
+ {
+ $connection = getConnection();
+ $connection->setOption(ConnectionOptions::OPTION_CONNECT_TIMEOUT, 3);
+ $connection->setOption(ConnectionOptions::OPTION_REQUEST_TIMEOUT, 2);
+ $query = 'RETURN SLEEP(3)';
+
+ $statement = new Statement($connection, ['query' => $query]);
+
+ try {
+ // this is expected to fail
+ $statement->execute();
+ } catch (ClientException $exception) {
+ static::assertEquals(408, $exception->getCode());
+ throw $exception;
+ }
+ }
+
+ /**
+ * Test request timeout, no exception
+ */
+ public function testSetRequestTimeout()
+ {
+ $connection = getConnection();
+ $connection->setOption(ConnectionOptions::OPTION_CONNECT_TIMEOUT, 5);
+ $connection->setOption(ConnectionOptions::OPTION_REQUEST_TIMEOUT, 5);
+ $query = 'RETURN SLEEP(1)';
+
+ $statement = new Statement($connection, ['query' => $query]);
+
+ // should work
+ $cursor = $statement->execute();
+ static::assertCount(1, $cursor->getAll());
+ }
/**
* Test "connection: close"
diff --git a/tests/QueryTest.php b/tests/QueryTest.php
index f9c1c1b7..ab3922a9 100644
--- a/tests/QueryTest.php
+++ b/tests/QueryTest.php
@@ -124,17 +124,17 @@ public function testGetSlow()
*/
public function testTimeoutException()
{
- $old = $this->connection->getOption(ConnectionOptions::OPTION_TIMEOUT);
- $this->connection->setOption(ConnectionOptions::OPTION_TIMEOUT, 10);
+ $old = $this->connection->getOption(ConnectionOptions::OPTION_REQUEST_TIMEOUT);
+ $this->connection->setOption(ConnectionOptions::OPTION_REQUEST_TIMEOUT, 10);
$query = 'RETURN SLEEP(13)';
$statement = new Statement($this->connection, ['query' => $query]);
try {
$statement->execute();
- $this->connection->setOption(ConnectionOptions::OPTION_TIMEOUT, $old);
+ $this->connection->setOption(ConnectionOptions::OPTION_REQUEST_TIMEOUT, $old);
} catch (ClientException $exception) {
- $this->connection->setOption(ConnectionOptions::OPTION_TIMEOUT, $old);
+ $this->connection->setOption(ConnectionOptions::OPTION_REQUEST_TIMEOUT, $old);
static::assertEquals($exception->getCode(), 408);
throw $exception;
}
From 2c3e1499b3a0a64ac6158cfe0e1249394799dab9 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Wed, 9 Jun 2021 21:21:17 +0200
Subject: [PATCH 29/35] adjust init file too
---
examples/init.php | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/examples/init.php b/examples/init.php
index 61ddc258..8a01f4db 100644
--- a/examples/init.php
+++ b/examples/init.php
@@ -59,8 +59,9 @@
// in order to use an externally generated JWT, there is no need to specify user and passwd, but just the JWT value:
// ConnectionOptions::OPTION_AUTH_JWT => '', // use an externally generated JWT for authorization
- ConnectionOptions::OPTION_TIMEOUT => 30, // timeout in seconds
- // ConnectionOptions::OPTION_TRACE => $traceFunc, // tracer function, can be used for debugging
+ ConnectionOptions::OPTION_CONNECT_TIMEOUT => 10, // connect timeout in seconds
+ ConnectionOptions::OPTION_REQUEST_TIMEOUT => 30, // request timeout in seconds
+ // 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
];
From 2f9a08b57557ec2fecede03211e12d12e82acc82 Mon Sep 17 00:00:00 2001
From: Jan
Date: Wed, 9 Jun 2021 21:22:57 +0200
Subject: [PATCH 30/35] issue #282: add extra length check (#285)
work around short/truncated reads by adding an extra length check rather
than assuming the read string already has a specific length
---
lib/ArangoDBClient/HttpHelper.php | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/lib/ArangoDBClient/HttpHelper.php b/lib/ArangoDBClient/HttpHelper.php
index 8ef9e67c..e768f601 100644
--- a/lib/ArangoDBClient/HttpHelper.php
+++ b/lib/ArangoDBClient/HttpHelper.php
@@ -255,7 +255,10 @@ public static function transfer($socket, $request, $method)
// 12 = minimum offset (i.e. strlen("HTTP/1.1 xxx") -
// after that we could see "content-length:"
- $pos = stripos($result, 'content-length: ', 12);
+ $pos = false;
+ if (strlen($result) > 12) {
+ $pos = stripos($result, 'content-length: ', 12);
+ }
if ($pos !== false) {
$contentLength = (int) substr($result, $pos + 16, 10); // 16 = strlen("content-length: ")
From 30494762761174dc47ba4b68532a429d4de66fdb Mon Sep 17 00:00:00 2001
From: Jan
Date: Wed, 9 Jun 2021 21:25:22 +0200
Subject: [PATCH 31/35] issue #269: Undefined index: revision (#284)
Fix an undefined index notice/warning when calling the
DocumentHandler::put function with a `revision` option set.
---
lib/ArangoDBClient/DocumentHandler.php | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/lib/ArangoDBClient/DocumentHandler.php b/lib/ArangoDBClient/DocumentHandler.php
index 948dc512..2024e269 100644
--- a/lib/ArangoDBClient/DocumentHandler.php
+++ b/lib/ArangoDBClient/DocumentHandler.php
@@ -757,9 +757,10 @@ protected function put($url, $collection, $documentId, Document $document, array
if (isset($params[ConnectionOptions::OPTION_REPLACE_POLICY]) &&
$params[ConnectionOptions::OPTION_REPLACE_POLICY] === UpdatePolicy::ERROR
) {
- if (null !== $options['revision']) {
+ $revision = $document->getRevision();
+ if (null !== $revision) {
$params['ignoreRevs'] = false;
- $headers['if-match'] = '"' . $options['revision'] . '"';
+ $headers['if-match'] = '"' . $revision . '"';
}
}
From 7cc3292f7c48b3b8c0628b38a7df570e79ce97be Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Fri, 18 Jun 2021 14:03:18 +0200
Subject: [PATCH 32/35] update travis ci badges
---
CHANGELOG.md | 9 +++++++++
README.md | 10 ++++------
2 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 326b3934..943bef9b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,15 @@
## Release notes for the ArangoDB-PHP driver 3.8.x
+OPTION_TIMEOUT of the Connection class is now superseded by the more specialized options
+- OPTION_CONNECT_TIMEOUT
+- OPTION_REQUEST_TIMEOUT
+
+The existing OPTION_TIMEOUT value can still be used as before. When used, it will
+automatically clobber values set in OPTION_CONNECT_TIMEOUT and OPTION_REQUEST_TIMEOUT.
+Using one of the more specialized options will remove OPTION_TIMEOUT from the connection's
+options.
+
Added CollectionHandler::insertMany() method to simplify insertion of multiple documents
at once. This is now the preferred way of inserting mutliple documents in a single
request, and will perform much better than using the `Batch` functionality, which is
diff --git a/README.md b/README.md
index 2566922b..e5c17944 100644
--- a/README.md
+++ b/README.md
@@ -4,12 +4,10 @@
The official ArangoDB PHP Driver.
-3.4: [](https://travis-ci.org/arangodb/arangodb-php)
-3.5: [](https://travis-ci.org/arangodb/arangodb-php)
-3.6: [](https://travis-ci.org/arangodb/arangodb-php)
-3.7: [](https://travis-ci.org/arangodb/arangodb-php)
-3.8: [](https://travis-ci.org/arangodb/arangodb-php)
-devel: [](https://travis-ci.org/arangodb/arangodb-php)
+3.6: [](https://travis-ci.com/arangodb/arangodb-php)
+3.7: [](https://travis-ci.com/arangodb/arangodb-php)
+3.8: [](https://travis-ci.com/arangodb/arangodb-php)
+devel: [(https://travis-ci.com/arangodb/arangodb-php)
- [Getting Started](https://www.arangodb.com/docs/stable/drivers/php-getting-started.html)
- [Tutorial](https://www.arangodb.com/docs/stable/drivers/php-tutorial.html)
From 5104c4e2803d8b7fab97a0c80a3abe3f3ff3253e Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Fri, 18 Jun 2021 14:05:40 +0200
Subject: [PATCH 33/35] add missing parenthesis
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index e5c17944..3ccb423a 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ The official ArangoDB PHP Driver.
3.6: [](https://travis-ci.com/arangodb/arangodb-php)
3.7: [](https://travis-ci.com/arangodb/arangodb-php)
3.8: [](https://travis-ci.com/arangodb/arangodb-php)
-devel: [(https://travis-ci.com/arangodb/arangodb-php)
+devel: [](https://travis-ci.com/arangodb/arangodb-php)
- [Getting Started](https://www.arangodb.com/docs/stable/drivers/php-getting-started.html)
- [Tutorial](https://www.arangodb.com/docs/stable/drivers/php-tutorial.html)
From d5f7829e9e94351d213b4df0e4a5227a4b506e16 Mon Sep 17 00:00:00 2001
From: jsteemann
Date: Thu, 21 Oct 2021 17:12:22 +0200
Subject: [PATCH 34/35] enable extra analyzer tests
---
tests/AnalyzerTest.php | 20 ++++++++------------
1 file changed, 8 insertions(+), 12 deletions(-)
diff --git a/tests/AnalyzerTest.php b/tests/AnalyzerTest.php
index 5e2d7ecb..c1294aae 100644
--- a/tests/AnalyzerTest.php
+++ b/tests/AnalyzerTest.php
@@ -43,7 +43,7 @@ public function testCreateAnalyzerObject()
$analyzer = new Analyzer('Analyzer1' . '_' . static::$testsTimestamp, 'text', [ "locale" => "en.UTF-8", "stopwords" => [] ]);
static::assertEquals('Analyzer1' . '_' . static::$testsTimestamp, $analyzer->getName());
static::assertEquals('text', $analyzer->getType());
- static::assertEquals([ "locale" => "en.UTF-8", "stopwords" => [] ],$analyzer->getProperties());
+ static::assertEquals([ "locale" => "en.UTF-8", "stopwords" => [] ], $analyzer->getProperties());
static::assertEquals([], $analyzer->getFeatures());
}
@@ -69,7 +69,7 @@ public function testCreateTextAnalyzer()
$result = $this->analyzerHandler->create($analyzer);
static::assertEquals('Analyzer1' . '_' . static::$testsTimestamp, $result['name']);
static::assertEquals('text', $result['type']);
- static::assertEquals([ "locale" => "en.UTF-8", "stopwords" => [] ],$analyzer->getProperties());
+ static::assertEquals([ "locale" => "en.UTF-8", "stopwords" => [] ], $analyzer->getProperties());
static::assertEquals([], $analyzer->getFeatures());
}
@@ -89,17 +89,15 @@ public function testCreateTextAnalyzerFail()
/**
* Test creation of stopwords analyzer
*/
- /* disabled until 3.8.1
public function testCreateStopwordsAnalyzer()
{
$analyzer = new Analyzer('Analyzer1' . '_' . static::$testsTimestamp, 'stopwords', [ "stopwords" => ["foo", "bar", "baz", "dead"] ]);
$result = $this->analyzerHandler->create($analyzer);
static::assertEquals('Analyzer1' . '_' . static::$testsTimestamp, $result['name']);
static::assertEquals('stopwords', $result['type']);
- static::assertEquals([ "stopwords" => ["foo", "bar", "baz", "dead"] ],$analyzer->getProperties());
+ static::assertEquals([ "stopwords" => ["foo", "bar", "baz", "dead"] ], $analyzer->getProperties());
static::assertEquals([], $analyzer->getFeatures());
}
- */
/**
* Test creation of delimiter analyzer
@@ -110,7 +108,7 @@ public function testCreateDelimiterAnalyzer()
$result = $this->analyzerHandler->create($analyzer);
static::assertEquals('Analyzer1' . '_' . static::$testsTimestamp, $result['name']);
static::assertEquals('delimiter', $result['type']);
- static::assertEquals([ "delimiter" => " " ],$analyzer->getProperties());
+ static::assertEquals([ "delimiter" => " " ], $analyzer->getProperties());
static::assertEquals([], $analyzer->getFeatures());
}
@@ -123,14 +121,13 @@ public function testCreateNormAnalyzer()
$result = $this->analyzerHandler->create($analyzer);
static::assertEquals('Analyzer1' . '_' . static::$testsTimestamp, $result['name']);
static::assertEquals('norm', $result['type']);
- static::assertEquals([ "locale" => "en.UTF-8", "accent" => false, "case" => "lower" ],$analyzer->getProperties());
+ static::assertEquals([ "locale" => "en.UTF-8", "accent" => false, "case" => "lower" ], $analyzer->getProperties());
static::assertEquals([], $analyzer->getFeatures());
}
/**
* Test creation of pipeline analyzer
*/
- /* disabled until 3.8.1
public function testCreatePipelineAnalyzer()
{
$data = [ "pipeline" => [
@@ -147,7 +144,6 @@ public function testCreatePipelineAnalyzer()
static::assertEquals($data, $analyzer->getProperties());
static::assertEquals([], $analyzer->getFeatures());
}
- */
/**
* Test getting an analyzer
@@ -160,7 +156,7 @@ public function testGetAnalyzer()
$result = $this->analyzerHandler->get('Analyzer1' . '_' . static::$testsTimestamp);
static::assertEquals('_system::Analyzer1' . '_' . static::$testsTimestamp, $result->getName());
static::assertEquals('text', $result->getType());
- static::assertEquals([ "locale" => "en.UTF-8", "stopwords" => [] ],$analyzer->getProperties());
+ static::assertEquals([ "locale" => "en.UTF-8", "stopwords" => [] ], $analyzer->getProperties());
static::assertEquals([], $analyzer->getFeatures());
}
@@ -252,13 +248,13 @@ public function testAnalyzerProperties()
$result = $this->analyzerHandler->create($analyzer);
static::assertEquals('Analyzer2' . '_' . static::$testsTimestamp, $result['name']);
static::assertEquals('text', $result['type']);
- static::assertEquals([ "locale" => "en.UTF-8", "stopwords" => [] ],$analyzer->getProperties());
+ static::assertEquals([ "locale" => "en.UTF-8", "stopwords" => [] ], $analyzer->getProperties());
static::assertEquals([], $analyzer->getFeatures());
$result = $this->analyzerHandler->properties($analyzer);
static::assertEquals('_system::Analyzer2' . '_' . static::$testsTimestamp, $result['name']);
static::assertEquals('text', $result['type']);
- static::assertEquals([ "locale" => "en.UTF-8", "stopwords" => [] ],$analyzer->getProperties());
+ static::assertEquals([ "locale" => "en.UTF-8", "stopwords" => [] ], $analyzer->getProperties());
static::assertEquals([], $analyzer->getFeatures());
}
From 2b12ea6d78fde557c976a1d8fa16242fdc395bf6 Mon Sep 17 00:00:00 2001
From: Jan
Date: Fri, 21 Oct 2022 18:37:23 +0200
Subject: [PATCH 35/35] Feature 3.8/pull latest (#300)
---
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 e0983f2c..55229c51 100644
--- a/tests/travis/setup_arangodb.sh
+++ b/tests/travis/setup_arangodb.sh
@@ -50,8 +50,8 @@ echo "./phpunit --version"
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $DIR
-docker pull arangodb/arangodb-preview:3.8.0-nightly
-docker run -d -e ARANGO_ROOT_PASSWORD="test" -p 8529:8529 arangodb/arangodb-preview:3.8.0-nightly
+docker pull arangodb/arangodb-preview:3.8-nightly
+docker run -d -e ARANGO_ROOT_PASSWORD="test" -p 8529:8529 arangodb/arangodb-preview:3.8-nightly
sleep 2