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/CHANGELOG.md b/CHANGELOG.md
index ca92ca18..943bef9b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,17 +2,103 @@
## 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..
+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
+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:
+```
+$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.
-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".
+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"
+- "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()`.
+
+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:
+
+- 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:
+
+- 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 server-side options have been removed in class Collection:
+- isVolatile
+- journalSize
+
+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
diff --git a/README.md b/README.md
index 51f10fca..3ccb423a 100644
--- a/README.md
+++ b/README.md
@@ -4,11 +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)
-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)
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
- ConnectionOptions::OPTION_TIMEOUT => 30, // timeout in seconds
- // ConnectionOptions::OPTION_TRACE => $traceFunc, // tracer function, can be used for debugging
+ // 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_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
];
diff --git a/lib/ArangoDBClient/AdminHandler.php b/lib/ArangoDBClient/AdminHandler.php
index c80ef3a3..5383b481 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
@@ -111,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();
+ }
/**
@@ -138,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
@@ -159,6 +214,8 @@ public function getServerLog(array $options = [])
*
* @throws Exception
*
+ * @deprecated not necessary anymore
+ *
* @return bool
* @since 1.2
*/
@@ -168,6 +225,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 +343,8 @@ public function reloadServerRouting()
*
* @see getServerStatisticsDescription()
*
+ * @deprecated use metrics API instead
+ *
* @since 1.3
*/
public function getServerStatistics()
@@ -224,6 +379,8 @@ public function getServerStatistics()
*
* @see getServerStatistics()
*
+ * @deprecated use metrics API instead
+ *
* @since 1.3
*/
public function getServerStatisticsDescription(array $options = [])
diff --git a/lib/ArangoDBClient/Collection.php b/lib/ArangoDBClient/Collection.php
index c7c484fb..3872cb60 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
*
@@ -872,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)
@@ -893,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/CollectionHandler.php b/lib/ArangoDBClient/CollectionHandler.php
index 0794a70d..3ece4e03 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';
@@ -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();
@@ -341,7 +332,7 @@ public function create($collection, array $options = [])
}
/**
- * unload option
+ * unload option (deprecated)
*/
const OPTION_UNLOAD = 'unload';
@@ -403,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];
}
@@ -567,6 +557,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 +580,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/Connection.php b/lib/ArangoDBClient/Connection.php
index 5478a324..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);
@@ -424,13 +425,37 @@ 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])) {
- // add authorization header
- $authorizationValue = base64_encode(
- $this->_options[ConnectionOptions::OPTION_AUTH_USER] . ':' .
- $this->_options[ConnectionOptions::OPTION_AUTH_PASSWD]
+ 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 secret
+ $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],
@@ -888,24 +913,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/ConnectionOptions.php b/lib/ArangoDBClient/ConnectionOptions.php
index f68dd1df..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
@@ -165,20 +176,15 @@ 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
+ * Authentication JWT
*/
- const OPTION_IS_VOLATILE = 'isVolatile';
+ const OPTION_AUTH_JWT = 'Jwt';
/**
* Authentication user name
@@ -298,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();
}
@@ -444,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',
@@ -456,9 +468,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 +496,7 @@ private static function getDefaults()
*/
private static function getSupportedAuthTypes()
{
- return ['Basic'];
+ return ['Basic', 'Bearer'];
}
/**
@@ -582,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/Cursor.php b/lib/ArangoDBClient/Cursor.php
index 04fb1bd8..816449cc 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();
@@ -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/lib/ArangoDBClient/DefaultValues.php b/lib/ArangoDBClient/DefaultValues.php
index b01d62f0..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
@@ -51,16 +62,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)
*/
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..2024e269 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';
/**
@@ -292,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 = [])
@@ -331,9 +342,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
@@ -341,37 +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;
-
- $params = $this->includeOptionsInParams(
- $options, [
- 'waitForSync' => null,
- 'silent' => false,
- 'overwrite' => (bool) @$options[self::OPTION_OVERWRITE],
- '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
@@ -380,26 +371,33 @@ public function insert($collection, $document, array $options = [])
if ($batchPart = $response->getBatchPart()) {
return $batchPart;
}
+
+ if (@$options[self::OPTION_SILENT]) {
+ // nothing will be returned here
+ return null;
+ }
+
+ $json = $response->getJson();
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');
}
@@ -409,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 = [])
{
@@ -438,7 +537,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 +573,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 +598,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
*
@@ -507,13 +609,10 @@ protected function patch($url, $collection, $documentId, Document $document, arr
$this->addTransactionHeader($headers, $collection);
$collection = $this->makeCollection($collection);
- $_documentClass = $this->_documentClass;
$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,7 +636,14 @@ 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);
- $json = $result->getJson();
+
+ if (@$params[self::OPTION_SILENT]) {
+ // nothing will be returned here
+ return null;
+ }
+
+ $json = $result->getJson();
+ $_documentClass = $this->_documentClass;
$document->setRevision($json[$_documentClass::ENTRY_REV]);
if (@$options[self::OPTION_RETURN_OLD] || @$options[self::OPTION_RETURN_NEW]) {
@@ -566,6 +672,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 +707,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 +732,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
*
@@ -632,12 +743,10 @@ protected function put($url, $collection, $documentId, Document $document, array
$this->addTransactionHeader($headers, $collection);
$collection = $this->makeCollection($collection);
- $_documentClass = $this->_documentClass;
$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],
@@ -648,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 . '"';
}
}
@@ -659,7 +769,14 @@ 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);
- $json = $result->getJson();
+
+ if (@$params[self::OPTION_SILENT]) {
+ // nothing will be returned here
+ return null;
+ }
+
+ $json = $result->getJson();
+ $_documentClass = $this->_documentClass;
$document->setRevision($json[$_documentClass::ENTRY_REV]);
if (@$options[self::OPTION_RETURN_OLD] || @$options[self::OPTION_RETURN_NEW]) {
@@ -680,6 +797,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 +823,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 +841,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 +857,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..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);
}
@@ -100,19 +101,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/lib/ArangoDBClient/HttpHelper.php b/lib/ArangoDBClient/HttpHelper.php
index 7395e5fe..e768f601 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;
}
@@ -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: ")
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/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
*/
diff --git a/lib/ArangoDBClient/Urls.php b/lib/ArangoDBClient/Urls.php
index 5b1e7c9d..252e9754 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
@@ -194,27 +199,37 @@ 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
+ * 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..32fa5ad8 100644
--- a/tests/AdminTest.php
+++ b/tests/AdminTest.php
@@ -21,6 +21,39 @@ 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));
+
+ 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"]));
+ }
+ }
/**
@@ -63,6 +96,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['messages']), 'Should be an array');
+
+ foreach ($result['messages'] as $entry) {
+ static::assertArrayHasKey('id', $entry);
+ static::assertArrayHasKey('topic', $entry);
+ static::assertArrayHasKey('level', $entry);
+ static::assertArrayHasKey('date', $entry);
+ static::assertArrayHasKey('message', $entry);
+ }
+ }
/**
@@ -133,6 +185,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"]));
+ }
/**
diff --git a/tests/AnalyzerTest.php b/tests/AnalyzerTest.php
index f7bec85b..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());
}
@@ -85,6 +85,65 @@ public function testCreateTextAnalyzerFail()
}
static::assertEquals(400, $exception->getCode());
}
+
+ /**
+ * Test creation of stopwords analyzer
+ */
+ 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
+ */
+ 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
@@ -97,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());
}
@@ -189,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());
}
diff --git a/tests/CollectionBasicTest.php b/tests/CollectionBasicTest.php
index 48bd5d72..fb254e94 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");
}
@@ -87,6 +86,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());
+ }
/**
@@ -692,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
*/
@@ -1265,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
*/
diff --git a/tests/CollectionExtendedTest.php b/tests/CollectionExtendedTest.php
index 7063562d..76455bbb 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!');
@@ -2543,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!');
+ }
/**
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/DocumentBasicTest.php b/tests/DocumentBasicTest.php
index dde3ef82..ea87a47f 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
*/
@@ -73,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']);
@@ -82,7 +120,237 @@ public function testInsertReturnNew()
/**
- * Try to create a document and overwrite it
+ * 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));
+
+ if (isCluster($this->connection)) {
+ static::assertEquals(4, count($result));
+ } else {
+ 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));
+
+ 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']);
+ }
+ }
+ }
+
+
+ /**
+ * 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
*/
public function testInsertOverwrite()
{
@@ -91,21 +359,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 +397,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 +503,7 @@ public function testCreateAndDeleteDocumentWithId()
$id = $resultingDocument->getHandle();
static::assertSame($collection->getName() . '/' . $key, $id);
- $documentHandler->remove($document);
+ static::assertTrue($documentHandler->remove($document));
}
@@ -165,7 +526,49 @@ 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]));
+ }
+
+
+ /**
+ * 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());
}
@@ -231,7 +634,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']);
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;
}
diff --git a/tests/StatementTest.php b/tests/StatementTest.php
index 716d2795..7961fea7 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());
+ }
/**
@@ -498,6 +542,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;
@@ -529,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;
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
diff --git a/tests/travis/setup_arangodb.sh b/tests/travis/setup_arangodb.sh
index 8a57af44..55229c51 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
@@ -35,8 +50,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:devel-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