MDL-67385 libraries: Upgrde mongodb to version 1.5.1

This commit is contained in:
Shamim Rezaie 2019-12-16 17:42:08 +11:00
parent 5a28e4ec05
commit 02c64a4c1b
75 changed files with 2453 additions and 1075 deletions

View File

@ -25,13 +25,16 @@ use MongoDB\Exception\BadMethodCallException;
*/
class BulkWriteResult
{
/** @var WriteResult */
private $writeResult;
/** @var mixed[] */
private $insertedIds;
/** @var boolean */
private $isAcknowledged;
/**
* Constructor.
*
* @param WriteResult $writeResult
* @param mixed[] $insertedIds
*/

View File

@ -17,15 +17,15 @@
namespace MongoDB;
use MongoDB\BSON\Serializable;
use MongoDB\Driver\Cursor;
use Iterator;
use MongoDB\Driver\CursorId;
use MongoDB\Driver\Exception\ConnectionException;
use MongoDB\Driver\Exception\RuntimeException;
use MongoDB\Driver\Exception\ServerException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\ResumeTokenException;
use IteratorIterator;
use Iterator;
use MongoDB\Model\ChangeStreamIterator;
use function call_user_func;
use function in_array;
/**
* Iterator for a change stream.
@ -42,27 +42,39 @@ class ChangeStream implements Iterator
*/
const CURSOR_NOT_FOUND = 43;
private static $errorCodeCappedPositionLost = 136;
private static $errorCodeInterrupted = 11601;
private static $errorCodeCursorKilled = 237;
/** @var array */
private static $nonResumableErrorCodes = [
136, // CappedPositionLost
237, // CursorKilled
11601, // Interrupted
];
private $resumeToken;
/** @var callable */
private $resumeCallable;
private $csIt;
/** @var ChangeStreamIterator */
private $iterator;
/** @var integer */
private $key = 0;
/**
* Whether the change stream has advanced to its first result. This is used
* to determine whether $key should be incremented after an iteration event.
*
* @var boolean
*/
private $hasAdvanced = false;
/**
* Constructor.
*
* @internal
* @param Cursor $cursor
* @param callable $resumeCallable
* @param ChangeStreamIterator $iterator
* @param callable $resumeCallable
*/
public function __construct(Cursor $cursor, callable $resumeCallable)
public function __construct(ChangeStreamIterator $iterator, callable $resumeCallable)
{
$this->iterator = $iterator;
$this->resumeCallable = $resumeCallable;
$this->csIt = new IteratorIterator($cursor);
}
/**
@ -71,15 +83,29 @@ class ChangeStream implements Iterator
*/
public function current()
{
return $this->csIt->current();
return $this->iterator->current();
}
/**
* @return \MongoDB\Driver\CursorId
* @return CursorId
*/
public function getCursorId()
{
return $this->csIt->getInnerIterator()->getId();
return $this->iterator->getInnerIterator()->getId();
}
/**
* Returns the resume token for the iterator's current position.
*
* Null may be returned if no change documents have been iterated and the
* server did not include a postBatchResumeToken in its aggregate or getMore
* command response.
*
* @return array|object|null
*/
public function getResumeToken()
{
return $this->iterator->getResumeToken();
}
/**
@ -91,60 +117,40 @@ class ChangeStream implements Iterator
if ($this->valid()) {
return $this->key;
}
return null;
}
/**
* @see http://php.net/iterator.next
* @return void
* @throws ResumeTokenException
*/
public function next()
{
try {
$this->csIt->next();
if ($this->valid()) {
if ($this->hasAdvanced) {
$this->key++;
}
$this->hasAdvanced = true;
$this->resumeToken = $this->extractResumeToken($this->csIt->current());
}
/* If the cursorId is 0, the server has invalidated the cursor so we
* will never perform another getMore. This means that we cannot
* resume and we can therefore unset the resumeCallable, which will
* free any reference to Watch. This will also free the only
* reference to an implicit session, since any such reference
* belongs to Watch. */
if ((string) $this->getCursorId() === '0') {
$this->resumeCallable = null;
}
$this->iterator->next();
$this->onIteration($this->hasAdvanced);
} catch (RuntimeException $e) {
if ($this->isResumableError($e)) {
$this->resume();
}
$this->resumeOrThrow($e);
}
}
/**
* @see http://php.net/iterator.rewind
* @return void
* @throws ResumeTokenException
*/
public function rewind()
{
try {
$this->csIt->rewind();
if ($this->valid()) {
$this->hasAdvanced = true;
$this->resumeToken = $this->extractResumeToken($this->csIt->current());
}
// As with next(), free the callable once we know it will never be used.
if ((string) $this->getCursorId() === '0') {
$this->resumeCallable = null;
}
$this->iterator->rewind();
/* Unlike next() and resume(), the decision to increment the key
* does not depend on whether the change stream has advanced. This
* ensures that multiple calls to rewind() do not alter state. */
$this->onIteration(false);
} catch (RuntimeException $e) {
if ($this->isResumableError($e)) {
$this->resume();
}
$this->resumeOrThrow($e);
}
}
@ -154,40 +160,7 @@ class ChangeStream implements Iterator
*/
public function valid()
{
return $this->csIt->valid();
}
/**
* Extracts the resume token (i.e. "_id" field) from the change document.
*
* @param array|document $document Change document
* @return mixed
* @throws InvalidArgumentException
* @throws ResumeTokenException if the resume token is not found or invalid
*/
private function extractResumeToken($document)
{
if ( ! is_array($document) && ! is_object($document)) {
throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
}
if ($document instanceof Serializable) {
return $this->extractResumeToken($document->bsonSerialize());
}
$resumeToken = is_array($document)
? (isset($document['_id']) ? $document['_id'] : null)
: (isset($document->_id) ? $document->_id : null);
if ( ! isset($resumeToken)) {
throw ResumeTokenException::notFound();
}
if ( ! is_array($resumeToken) && ! is_object($resumeToken)) {
throw ResumeTokenException::invalidType($resumeToken);
}
return $resumeToken;
return $this->iterator->valid();
}
/**
@ -203,11 +176,15 @@ class ChangeStream implements Iterator
return true;
}
if ( ! $exception instanceof ServerException) {
if (! $exception instanceof ServerException) {
return false;
}
if (in_array($exception->getCode(), [self::$errorCodeCappedPositionLost, self::$errorCodeCursorKilled, self::$errorCodeInterrupted])) {
if ($exception->hasErrorLabel('NonResumableChangeStreamError')) {
return false;
}
if (in_array($exception->getCode(), self::$nonResumableErrorCodes)) {
return false;
}
@ -215,14 +192,63 @@ class ChangeStream implements Iterator
}
/**
* Creates a new changeStream after a resumable server error.
* Perform housekeeping after an iteration event.
*
* @param boolean $incrementKey Increment $key if there is a current result
* @throws ResumeTokenException
*/
private function onIteration($incrementKey)
{
/* If the cursorId is 0, the server has invalidated the cursor and we
* will never perform another getMore nor need to resume since any
* remaining results (up to and including the invalidate event) will
* have been received in the last response. Therefore, we can unset the
* resumeCallable. This will free any reference to Watch as well as the
* only reference to any implicit session created therein. */
if ((string) $this->getCursorId() === '0') {
$this->resumeCallable = null;
}
/* Return early if there is not a current result. Avoid any attempt to
* increment the iterator's key. */
if (! $this->valid()) {
return;
}
if ($incrementKey) {
$this->key++;
}
$this->hasAdvanced = true;
}
/**
* Recreates the ChangeStreamIterator after a resumable server error.
*
* @return void
*/
private function resume()
{
$newChangeStream = call_user_func($this->resumeCallable, $this->resumeToken);
$this->csIt = $newChangeStream->csIt;
$this->csIt->rewind();
$this->iterator = call_user_func($this->resumeCallable, $this->getResumeToken(), $this->hasAdvanced);
$this->iterator->rewind();
$this->onIteration($this->hasAdvanced);
}
/**
* Either resumes after a resumable error or re-throws the exception.
*
* @param RuntimeException $exception
* @throws RuntimeException
*/
private function resumeOrThrow(RuntimeException $exception)
{
if ($this->isResumableError($exception)) {
$this->resume();
return;
}
throw $exception;
}
}

View File

@ -17,35 +17,55 @@
namespace MongoDB;
use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Model\BSONArray;
use MongoDB\Model\BSONDocument;
use MongoDB\Model\DatabaseInfoIterator;
use MongoDB\Operation\DropDatabase;
use MongoDB\Operation\ListDatabases;
use MongoDB\Operation\Watch;
use function is_array;
class Client
{
/** @var array */
private static $defaultTypeMap = [
'array' => 'MongoDB\Model\BSONArray',
'document' => 'MongoDB\Model\BSONDocument',
'root' => 'MongoDB\Model\BSONDocument',
'array' => BSONArray::class,
'document' => BSONDocument::class,
'root' => BSONDocument::class,
];
/** @var integer */
private static $wireVersionForReadConcern = 4;
/** @var integer */
private static $wireVersionForWritableCommandWriteConcern = 5;
/** @var Manager */
private $manager;
/** @var ReadConcern */
private $readConcern;
/** @var ReadPreference */
private $readPreference;
/** @var string */
private $uri;
/** @var array */
private $typeMap;
/** @var WriteConcern */
private $writeConcern;
/**
@ -146,13 +166,13 @@ class Client
*/
public function dropDatabase($databaseName, array $options = [])
{
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
@ -217,6 +237,7 @@ class Client
* List databases.
*
* @see ListDatabases::__construct() for supported options
* @param array $options
* @return DatabaseInfoIterator
* @throws UnexpectedValueException if the command response was malformed
* @throws InvalidArgumentException for parameter/option parsing errors
@ -225,7 +246,7 @@ class Client
public function listDatabases(array $options = [])
{
$operation = new ListDatabases($options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
return $operation->execute($server);
}
@ -267,8 +288,8 @@ class Client
* Start a new client session.
*
* @see http://php.net/manual/en/mongodb-driver-manager.startsession.php
* @param array $options Session options
* @return MongoDB\Driver\Session
* @param array $options Session options
* @return Session
*/
public function startSession(array $options = [])
{
@ -286,17 +307,17 @@ class Client
*/
public function watch(array $pipeline = [], array $options = [])
{
if ( ! isset($options['readPreference'])) {
if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
$options['readPreference'] = $this->readPreference;
}
$server = $this->manager->selectServer($options['readPreference']);
$server = select_server($this->manager, $options);
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
$options['readConcern'] = $this->readConcern;
}
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}

View File

@ -18,24 +18,24 @@
namespace MongoDB;
use MongoDB\BSON\JavascriptInterface;
use MongoDB\BSON\Serializable;
use MongoDB\ChangeStream;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Model\BSONArray;
use MongoDB\Model\BSONDocument;
use MongoDB\Model\IndexInfo;
use MongoDB\Model\IndexInfoIterator;
use MongoDB\Operation\Aggregate;
use MongoDB\Operation\BulkWrite;
use MongoDB\Operation\CreateIndexes;
use MongoDB\Operation\Count;
use MongoDB\Operation\CountDocuments;
use MongoDB\Operation\CreateIndexes;
use MongoDB\Operation\DeleteMany;
use MongoDB\Operation\DeleteOne;
use MongoDB\Operation\Distinct;
@ -58,24 +58,52 @@ use MongoDB\Operation\UpdateMany;
use MongoDB\Operation\UpdateOne;
use MongoDB\Operation\Watch;
use Traversable;
use function array_diff_key;
use function array_intersect_key;
use function current;
use function is_array;
use function strlen;
class Collection
{
/** @var array */
private static $defaultTypeMap = [
'array' => 'MongoDB\Model\BSONArray',
'document' => 'MongoDB\Model\BSONDocument',
'root' => 'MongoDB\Model\BSONDocument',
'array' => BSONArray::class,
'document' => BSONDocument::class,
'root' => BSONDocument::class,
];
/** @var integer */
private static $wireVersionForFindAndModifyWriteConcern = 4;
/** @var integer */
private static $wireVersionForReadConcern = 4;
/** @var integer */
private static $wireVersionForWritableCommandWriteConcern = 5;
/** @var integer */
private static $wireVersionForReadConcernWithWriteStage = 8;
/** @var string */
private $collectionName;
/** @var string */
private $databaseName;
/** @var Manager */
private $manager;
/** @var ReadConcern */
private $readConcern;
/** @var ReadPreference */
private $readPreference;
/** @var array */
private $typeMap;
/** @var WriteConcern */
private $writeConcern;
/**
@ -116,11 +144,11 @@ class Collection
}
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
@ -128,7 +156,7 @@ class Collection
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}
$this->manager = $manager;
@ -189,32 +217,39 @@ class Collection
*/
public function aggregate(array $pipeline, array $options = [])
{
$hasOutStage = \MongoDB\is_last_pipeline_operator_out($pipeline);
$hasWriteStage = is_last_pipeline_operator_write($pipeline);
if ( ! isset($options['readPreference'])) {
if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
$options['readPreference'] = $this->readPreference;
}
if ($hasOutStage) {
if ($hasWriteStage) {
$options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY);
}
$server = $this->manager->selectServer($options['readPreference']);
$server = select_server($this->manager, $options);
/* A "majority" read concern is not compatible with the $out stage, so
* avoid providing the Collection's read concern if it would conflict.
/* MongoDB 4.2 and later supports a read concern when an $out stage is
* being used, but earlier versions do not.
*
* A read concern is also not compatible with transactions.
*/
if ( ! isset($options['readConcern']) &&
! ($hasOutStage && $this->readConcern->getLevel() === ReadConcern::MAJORITY) &&
\MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
if (! isset($options['readConcern']) &&
server_supports_feature($server, self::$wireVersionForReadConcern) &&
! is_in_transaction($options) &&
( ! $hasWriteStage || server_supports_feature($server, self::$wireVersionForReadConcernWithWriteStage))
) {
$options['readConcern'] = $this->readConcern;
}
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
if ($hasOutStage && ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
if ($hasWriteStage &&
! isset($options['writeConcern']) &&
server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) &&
! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
@ -236,12 +271,12 @@ class Collection
*/
public function bulkWrite(array $operations, array $options = [])
{
if ( ! isset($options['writeConcern'])) {
if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
$operation = new BulkWrite($this->databaseName, $this->collectionName, $operations, $options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
return $operation->execute($server);
}
@ -262,13 +297,13 @@ class Collection
*/
public function count($filter = [], array $options = [])
{
if ( ! isset($options['readPreference'])) {
if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
$options['readPreference'] = $this->readPreference;
}
$server = $this->manager->selectServer($options['readPreference']);
$server = select_server($this->manager, $options);
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
$options['readConcern'] = $this->readConcern;
}
@ -291,13 +326,13 @@ class Collection
*/
public function countDocuments($filter = [], array $options = [])
{
if ( ! isset($options['readPreference'])) {
if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
$options['readPreference'] = $this->readPreference;
}
$server = $this->manager->selectServer($options['readPreference']);
$server = select_server($this->manager, $options);
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
$options['readConcern'] = $this->readConcern;
}
@ -357,9 +392,9 @@ class Collection
*/
public function createIndexes(array $indexes, array $options = [])
{
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
@ -382,12 +417,12 @@ class Collection
*/
public function deleteMany($filter, array $options = [])
{
if ( ! isset($options['writeConcern'])) {
if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
$operation = new DeleteMany($this->databaseName, $this->collectionName, $filter, $options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
return $operation->execute($server);
}
@ -406,12 +441,12 @@ class Collection
*/
public function deleteOne($filter, array $options = [])
{
if ( ! isset($options['writeConcern'])) {
if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
$operation = new DeleteOne($this->databaseName, $this->collectionName, $filter, $options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
return $operation->execute($server);
}
@ -420,9 +455,9 @@ class Collection
* Finds the distinct values for a specified field across the collection.
*
* @see Distinct::__construct() for supported options
* @param string $fieldName Field for which to return distinct values
* @param array|object $filter Query by which to filter documents
* @param array $options Command options
* @param string $fieldName Field for which to return distinct values
* @param array|object $filter Query by which to filter documents
* @param array $options Command options
* @return mixed[]
* @throws UnexpectedValueException if the command response was malformed
* @throws UnsupportedException if options are not supported by the selected server
@ -431,13 +466,17 @@ class Collection
*/
public function distinct($fieldName, $filter = [], array $options = [])
{
if ( ! isset($options['readPreference'])) {
if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
$options['readPreference'] = $this->readPreference;
}
$server = $this->manager->selectServer($options['readPreference']);
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
$server = select_server($this->manager, $options);
if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
$options['readConcern'] = $this->readConcern;
}
@ -458,13 +497,13 @@ class Collection
*/
public function drop(array $options = [])
{
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
@ -478,7 +517,7 @@ class Collection
*
* @see DropIndexes::__construct() for supported options
* @param string|IndexInfo $indexName Index name or model object
* @param array $options Additional options
* @param array $options Additional options
* @return array|object Command result document
* @throws UnsupportedException if options are not supported by the selected server
* @throws InvalidArgumentException for parameter/option parsing errors
@ -492,13 +531,13 @@ class Collection
throw new InvalidArgumentException('dropIndexes() must be used to drop multiple indexes');
}
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
@ -519,13 +558,13 @@ class Collection
*/
public function dropIndexes(array $options = [])
{
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
@ -545,15 +584,15 @@ class Collection
* @throws InvalidArgumentException for parameter/option parsing errors
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function EstimatedDocumentCount(array $options = [])
public function estimatedDocumentCount(array $options = [])
{
if ( ! isset($options['readPreference'])) {
if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
$options['readPreference'] = $this->readPreference;
}
$server = $this->manager->selectServer($options['readPreference']);
$server = select_server($this->manager, $options);
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
$options['readConcern'] = $this->readConcern;
}
@ -567,8 +606,8 @@ class Collection
*
* @see Explain::__construct() for supported options
* @see http://docs.mongodb.org/manual/reference/command/explain/
* @param Explainable $explainable Command on which to run explain
* @param array $options Additional options
* @param Explainable $explainable Command on which to run explain
* @param array $options Additional options
* @return array|object
* @throws UnsupportedException if explainable or options are not supported by the selected server
* @throws InvalidArgumentException for parameter/option parsing errors
@ -576,15 +615,15 @@ class Collection
*/
public function explain(Explainable $explainable, array $options = [])
{
if ( ! isset($options['readPreference'])) {
if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
$options['readPreference'] = $this->readPreference;
}
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$server = $this->manager->selectServer($options['readPreference']);
$server = select_server($this->manager, $options);
$operation = new Explain($this->databaseName, $explainable, $options);
@ -605,17 +644,17 @@ class Collection
*/
public function find($filter = [], array $options = [])
{
if ( ! isset($options['readPreference'])) {
if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
$options['readPreference'] = $this->readPreference;
}
$server = $this->manager->selectServer($options['readPreference']);
$server = select_server($this->manager, $options);
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
$options['readConcern'] = $this->readConcern;
}
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
@ -638,17 +677,17 @@ class Collection
*/
public function findOne($filter = [], array $options = [])
{
if ( ! isset($options['readPreference'])) {
if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
$options['readPreference'] = $this->readPreference;
}
$server = $this->manager->selectServer($options['readPreference']);
$server = select_server($this->manager, $options);
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
$options['readConcern'] = $this->readConcern;
}
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
@ -674,13 +713,13 @@ class Collection
*/
public function findOneAndDelete($filter, array $options = [])
{
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) {
if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
@ -711,13 +750,13 @@ class Collection
*/
public function findOneAndReplace($filter, $replacement, array $options = [])
{
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) {
if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
@ -748,13 +787,13 @@ class Collection
*/
public function findOneAndUpdate($filter, $update, array $options = [])
{
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) {
if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
@ -859,12 +898,12 @@ class Collection
*/
public function insertMany(array $documents, array $options = [])
{
if ( ! isset($options['writeConcern'])) {
if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
$operation = new InsertMany($this->databaseName, $this->collectionName, $documents, $options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
return $operation->execute($server);
}
@ -882,12 +921,12 @@ class Collection
*/
public function insertOne($document, array $options = [])
{
if ( ! isset($options['writeConcern'])) {
if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
$operation = new InsertOne($this->databaseName, $this->collectionName, $document, $options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
return $operation->execute($server);
}
@ -896,6 +935,7 @@ class Collection
* Returns information for all indexes for the collection.
*
* @see ListIndexes::__construct() for supported options
* @param array $options
* @return IndexInfoIterator
* @throws InvalidArgumentException for parameter/option parsing errors
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
@ -903,7 +943,7 @@ class Collection
public function listIndexes(array $options = [])
{
$operation = new ListIndexes($this->databaseName, $this->collectionName, $options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
return $operation->execute($server);
}
@ -913,10 +953,10 @@ class Collection
*
* @see MapReduce::__construct() for supported options
* @see http://docs.mongodb.org/manual/reference/command/mapReduce/
* @param JavascriptInterface $map Map function
* @param JavascriptInterface $reduce Reduce function
* @param string|array|object $out Output specification
* @param array $options Command options
* @param JavascriptInterface $map Map function
* @param JavascriptInterface $reduce Reduce function
* @param string|array|object $out Output specification
* @param array $options Command options
* @return MapReduceResult
* @throws UnsupportedException if options are not supported by the selected server
* @throws InvalidArgumentException for parameter/option parsing errors
@ -925,9 +965,9 @@ class Collection
*/
public function mapReduce(JavascriptInterface $map, JavascriptInterface $reduce, $out, array $options = [])
{
$hasOutputCollection = ! \MongoDB\is_mapreduce_output_inline($out);
$hasOutputCollection = ! is_mapreduce_output_inline($out);
if ( ! isset($options['readPreference'])) {
if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
$options['readPreference'] = $this->readPreference;
}
@ -936,20 +976,22 @@ class Collection
$options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY);
}
$server = $this->manager->selectServer($options['readPreference']);
$server = select_server($this->manager, $options);
/* A "majority" read concern is not compatible with inline output, so
* avoid providing the Collection's read concern if it would conflict.
*
* A read concern is also not compatible with transactions.
*/
if ( ! isset($options['readConcern']) && ! ($hasOutputCollection && $this->readConcern->getLevel() === ReadConcern::MAJORITY) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
if (! isset($options['readConcern']) && ! ($hasOutputCollection && $this->readConcern->getLevel() === ReadConcern::MAJORITY) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
$options['readConcern'] = $this->readConcern;
}
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
@ -973,12 +1015,12 @@ class Collection
*/
public function replaceOne($filter, $replacement, array $options = [])
{
if ( ! isset($options['writeConcern'])) {
if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
$operation = new ReplaceOne($this->databaseName, $this->collectionName, $filter, $replacement, $options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
return $operation->execute($server);
}
@ -998,12 +1040,12 @@ class Collection
*/
public function updateMany($filter, $update, array $options = [])
{
if ( ! isset($options['writeConcern'])) {
if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
$operation = new UpdateMany($this->databaseName, $this->collectionName, $filter, $update, $options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
return $operation->execute($server);
}
@ -1023,12 +1065,12 @@ class Collection
*/
public function updateOne($filter, $update, array $options = [])
{
if ( ! isset($options['writeConcern'])) {
if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
$operation = new UpdateOne($this->databaseName, $this->collectionName, $filter, $update, $options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
return $operation->execute($server);
}
@ -1044,11 +1086,11 @@ class Collection
*/
public function watch(array $pipeline = [], array $options = [])
{
if ( ! isset($options['readPreference'])) {
if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
$options['readPreference'] = $this->readPreference;
}
$server = $this->manager->selectServer($options['readPreference']);
$server = select_server($this->manager, $options);
/* Although change streams require a newer version of the server than
* read concerns, perform the usual wire version check before inheriting
@ -1057,11 +1099,11 @@ class Collection
* related to change streams being unsupported instead of an
* UnsupportedException regarding use of the "readConcern" option from
* the Aggregate operation class. */
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
$options['readConcern'] = $this->readConcern;
}
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}

View File

@ -17,17 +17,20 @@
namespace MongoDB;
use MongoDB\Collection;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\GridFS\Bucket;
use MongoDB\Model\BSONArray;
use MongoDB\Model\BSONDocument;
use MongoDB\Model\CollectionInfoIterator;
use MongoDB\Operation\Aggregate;
use MongoDB\Operation\CreateCollection;
use MongoDB\Operation\DatabaseCommand;
use MongoDB\Operation\DropCollection;
@ -35,22 +38,44 @@ use MongoDB\Operation\DropDatabase;
use MongoDB\Operation\ListCollections;
use MongoDB\Operation\ModifyCollection;
use MongoDB\Operation\Watch;
use Traversable;
use function is_array;
use function strlen;
class Database
{
/** @var array */
private static $defaultTypeMap = [
'array' => 'MongoDB\Model\BSONArray',
'document' => 'MongoDB\Model\BSONDocument',
'root' => 'MongoDB\Model\BSONDocument',
'array' => BSONArray::class,
'document' => BSONDocument::class,
'root' => BSONDocument::class,
];
/** @var integer */
private static $wireVersionForReadConcern = 4;
/** @var integer */
private static $wireVersionForWritableCommandWriteConcern = 5;
/** @var integer */
private static $wireVersionForReadConcernWithWriteStage = 8;
/** @var string */
private $databaseName;
/** @var Manager */
private $manager;
/** @var ReadConcern */
private $readConcern;
/** @var ReadPreference */
private $readPreference;
/** @var array */
private $typeMap;
/** @var WriteConcern */
private $writeConcern;
/**
@ -87,11 +112,11 @@ class Database
}
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
@ -99,7 +124,7 @@ class Database
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}
$this->manager = $manager;
@ -155,6 +180,63 @@ class Database
return $this->databaseName;
}
/**
* Runs an aggregation framework pipeline on the database for pipeline
* stages that do not require an underlying collection, such as $currentOp
* and $listLocalSessions. Requires MongoDB >= 3.6
*
* @see Aggregate::__construct() for supported options
* @param array $pipeline List of pipeline operations
* @param array $options Command options
* @return Traversable
* @throws UnexpectedValueException if the command response was malformed
* @throws UnsupportedException if options are not supported by the selected server
* @throws InvalidArgumentException for parameter/option parsing errors
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function aggregate(array $pipeline, array $options = [])
{
$hasWriteStage = is_last_pipeline_operator_write($pipeline);
if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
$options['readPreference'] = $this->readPreference;
}
if ($hasWriteStage) {
$options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY);
}
$server = select_server($this->manager, $options);
/* MongoDB 4.2 and later supports a read concern when an $out stage is
* being used, but earlier versions do not.
*
* A read concern is also not compatible with transactions.
*/
if (! isset($options['readConcern']) &&
server_supports_feature($server, self::$wireVersionForReadConcern) &&
! is_in_transaction($options) &&
( ! $hasWriteStage || server_supports_feature($server, self::$wireVersionForReadConcernWithWriteStage))
) {
$options['readConcern'] = $this->readConcern;
}
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
if ($hasWriteStage &&
! isset($options['writeConcern']) &&
server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) &&
! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
$operation = new Aggregate($this->databaseName, null, $pipeline, $options);
return $operation->execute($server);
}
/**
* Execute a command on this database.
*
@ -167,16 +249,16 @@ class Database
*/
public function command($command, array $options = [])
{
if ( ! isset($options['readPreference'])) {
if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
$options['readPreference'] = $this->readPreference;
}
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$operation = new DatabaseCommand($this->databaseName, $command, $options);
$server = $this->manager->selectServer($options['readPreference']);
$server = select_server($this->manager, $options);
return $operation->execute($server);
}
@ -194,13 +276,13 @@ class Database
*/
public function createCollection($collectionName, array $options = [])
{
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
@ -221,13 +303,13 @@ class Database
*/
public function drop(array $options = [])
{
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
@ -249,13 +331,13 @@ class Database
*/
public function dropCollection($collectionName, array $options = [])
{
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
@ -338,7 +420,7 @@ class Database
public function listCollections(array $options = [])
{
$operation = new ListCollections($this->databaseName, $options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
return $operation->execute($server);
}
@ -350,18 +432,19 @@ class Database
* @param string $collectionName Collection or view to modify
* @param array $collectionOptions Collection or view options to assign
* @param array $options Command options
* @return array|object
* @throws InvalidArgumentException for parameter/option parsing errors
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function modifyCollection($collectionName, array $collectionOptions, array $options = [])
{
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$server = select_server($this->manager, $options);
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) {
$options['writeConcern'] = $this->writeConcern;
}
@ -422,17 +505,17 @@ class Database
*/
public function watch(array $pipeline = [], array $options = [])
{
if ( ! isset($options['readPreference'])) {
if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
$options['readPreference'] = $this->readPreference;
}
$server = $this->manager->selectServer($options['readPreference']);
$server = select_server($this->manager, $options);
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
$options['readConcern'] = $this->readConcern;
}
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}

View File

@ -25,14 +25,12 @@ use MongoDB\Exception\BadMethodCallException;
*/
class DeleteResult
{
/** @var WriteResult */
private $writeResult;
/** @var boolean */
private $isAcknowledged;
/**
* Constructor.
*
* @param WriteResult $writeResult
*/
public function __construct(WriteResult $writeResult)
{
$this->writeResult = $writeResult;

View File

@ -17,7 +17,10 @@
namespace MongoDB\Exception;
class BadMethodCallException extends \BadMethodCallException implements Exception
use BadMethodCallException as BaseBadMethodCallException;
use function sprintf;
class BadMethodCallException extends BaseBadMethodCallException implements Exception
{
/**
* Thrown when a mutable method is invoked on an immutable object.

View File

@ -17,6 +17,8 @@
namespace MongoDB\Exception;
interface Exception extends \MongoDB\Driver\Exception\Exception
use MongoDB\Driver\Exception\Exception as DriverException;
interface Exception extends DriverException
{
}

View File

@ -17,7 +17,13 @@
namespace MongoDB\Exception;
class InvalidArgumentException extends \MongoDB\Driver\Exception\InvalidArgumentException implements Exception
use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException;
use function get_class;
use function gettype;
use function is_object;
use function sprintf;
class InvalidArgumentException extends DriverInvalidArgumentException implements Exception
{
/**
* Thrown when an argument or option has an invalid type.

View File

@ -17,7 +17,10 @@
namespace MongoDB\Exception;
class ResumeTokenException extends \Exception
use function gettype;
use function sprintf;
class ResumeTokenException extends RuntimeException
{
/**
* Thrown when a resume token has an invalid type.

View File

@ -17,6 +17,8 @@
namespace MongoDB\Exception;
class RuntimeException extends \MongoDB\Driver\Exception\RuntimeException implements Exception
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
class RuntimeException extends DriverRuntimeException implements Exception
{
}

View File

@ -17,6 +17,8 @@
namespace MongoDB\Exception;
class UnexpectedValueException extends \MongoDB\Driver\Exception\UnexpectedValueException implements Exception
use MongoDB\Driver\Exception\UnexpectedValueException as DriverUnexpectedValueException;
class UnexpectedValueException extends DriverUnexpectedValueException implements Exception
{
}

View File

@ -59,6 +59,16 @@ class UnsupportedException extends RuntimeException
return new static('Read concern is not supported by the server executing this command');
}
/**
* Thrown when a readConcern is used with a read operation in a transaction.
*
* @return self
*/
public static function readConcernNotSupportedInTransaction()
{
return new static('The "readConcern" option cannot be specified within a transaction. Instead, specify it when starting the transaction.');
}
/**
* Thrown when a command's writeConcern option is not supported by a server.
*
@ -68,4 +78,14 @@ class UnsupportedException extends RuntimeException
{
return new static('Write concern is not supported by the server executing this command');
}
/**
* Thrown when a writeConcern is used with a write operation in a transaction.
*
* @return self
*/
public static function writeConcernNotSupportedInTransaction()
{
return new static('The "writeConcern" option cannot be specified within a transaction. Instead, specify it when starting the transaction.');
}
}

View File

@ -19,16 +19,40 @@ namespace MongoDB\GridFS;
use MongoDB\Collection;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\GridFS\Exception\CorruptFileException;
use MongoDB\GridFS\Exception\FileNotFoundException;
use MongoDB\Model\BSONArray;
use MongoDB\Model\BSONDocument;
use MongoDB\Operation\Find;
use stdClass;
use function array_intersect_key;
use function fopen;
use function get_resource_type;
use function in_array;
use function is_array;
use function is_bool;
use function is_integer;
use function is_object;
use function is_resource;
use function is_string;
use function method_exists;
use function MongoDB\apply_type_map_to_document;
use function MongoDB\BSON\fromPHP;
use function MongoDB\BSON\toJSON;
use function property_exists;
use function sprintf;
use function stream_context_create;
use function stream_copy_to_stream;
use function stream_get_meta_data;
use function stream_get_wrappers;
use function urlencode;
/**
* Bucket provides a public API for interacting with the GridFS files and chunks
@ -38,24 +62,50 @@ use stdClass;
*/
class Bucket
{
/** @var string */
private static $defaultBucketName = 'fs';
/** @var integer */
private static $defaultChunkSizeBytes = 261120;
/** @var array */
private static $defaultTypeMap = [
'array' => 'MongoDB\Model\BSONArray',
'document' => 'MongoDB\Model\BSONDocument',
'root' => 'MongoDB\Model\BSONDocument',
'array' => BSONArray::class,
'document' => BSONDocument::class,
'root' => BSONDocument::class,
];
/** @var string */
private static $streamWrapperProtocol = 'gridfs';
/** @var CollectionWrapper */
private $collectionWrapper;
/** @var string */
private $databaseName;
/** @var Manager */
private $manager;
/** @var string */
private $bucketName;
/** @var boolean */
private $disableMD5;
/** @var integer */
private $chunkSizeBytes;
/** @var ReadConcern */
private $readConcern;
/** @var ReadPreference */
private $readPreference;
/** @var array */
private $typeMap;
/** @var WriteConcern */
private $writeConcern;
/**
@ -110,11 +160,11 @@ class Bucket
}
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
@ -122,7 +172,7 @@ class Bucket
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}
$this->manager = $manager;
@ -192,7 +242,7 @@ class Bucket
*/
public function downloadToStream($id, $destination)
{
if ( ! is_resource($destination) || get_resource_type($destination) != "stream") {
if (! is_resource($destination) || get_resource_type($destination) != "stream") {
throw InvalidArgumentException::invalidType('$destination', $destination, 'resource');
}
@ -227,7 +277,7 @@ class Bucket
*/
public function downloadToStreamByName($filename, $destination, array $options = [])
{
if ( ! is_resource($destination) || get_resource_type($destination) != "stream") {
if (! is_resource($destination) || get_resource_type($destination) != "stream") {
throw InvalidArgumentException::invalidType('$destination', $destination, 'resource');
}
@ -332,7 +382,7 @@ class Bucket
$file = $this->getRawFileDocumentForStream($stream);
// Filter the raw document through the specified type map
return \MongoDB\apply_type_map_to_document($file, $this->typeMap);
return apply_type_map_to_document($file, $this->typeMap);
}
/**
@ -352,9 +402,9 @@ class Bucket
* the root type so we can reliably access the ID.
*/
$typeMap = ['root' => 'stdClass'] + $this->typeMap;
$file = \MongoDB\apply_type_map_to_document($file, $typeMap);
$file = apply_type_map_to_document($file, $typeMap);
if ( ! isset($file->_id) && ! property_exists($file, '_id')) {
if (! isset($file->_id) && ! property_exists($file, '_id')) {
throw new CorruptFileException('file._id does not exist');
}
@ -531,7 +581,7 @@ class Bucket
? $updateResult->getMatchedCount() === 1
: $this->collectionWrapper->findFileById($id) !== null;
if ( ! $found) {
if (! $found) {
throw FileNotFoundException::byId($id, $this->getFilesNamespace());
}
}
@ -561,7 +611,7 @@ class Bucket
*/
public function uploadFromStream($filename, $source, array $options = [])
{
if ( ! is_resource($source) || get_resource_type($source) != "stream") {
if (! is_resource($source) || get_resource_type($source) != "stream") {
throw InvalidArgumentException::invalidType('$source', $source, 'resource');
}
@ -579,10 +629,10 @@ class Bucket
*/
private function createPathForFile(stdClass $file)
{
if ( ! is_object($file->_id) || method_exists($file->_id, '__toString')) {
if (! is_object($file->_id) || method_exists($file->_id, '__toString')) {
$id = (string) $file->_id;
} else {
$id = \MongoDB\BSON\toJSON(\MongoDB\BSON\fromPHP(['_id' => $file->_id]));
$id = toJSON(fromPHP(['_id' => $file->_id]));
}
return sprintf(
@ -631,14 +681,14 @@ class Bucket
*/
private function getRawFileDocumentForStream($stream)
{
if ( ! is_resource($stream) || get_resource_type($stream) != "stream") {
if (! is_resource($stream) || get_resource_type($stream) != "stream") {
throw InvalidArgumentException::invalidType('$stream', $stream, 'resource');
}
$metadata = stream_get_meta_data($stream);
if ( ! isset ($metadata['wrapper_data']) || ! $metadata['wrapper_data'] instanceof StreamWrapper) {
throw InvalidArgumentException::invalidType('$stream wrapper data', isset($metadata['wrapper_data']) ? $metadata['wrapper_data'] : null, 'MongoDB\Driver\GridFS\StreamWrapper');
if (! isset($metadata['wrapper_data']) || ! $metadata['wrapper_data'] instanceof StreamWrapper) {
throw InvalidArgumentException::invalidType('$stream wrapper data', isset($metadata['wrapper_data']) ? $metadata['wrapper_data'] : null, StreamWrapper::class);
}
return $metadata['wrapper_data']->getFile();

View File

@ -18,11 +18,14 @@
namespace MongoDB\GridFS;
use MongoDB\Collection;
use MongoDB\UpdateResult;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadPreference;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\UpdateResult;
use stdClass;
use function abs;
use function sprintf;
/**
* CollectionWrapper abstracts the GridFS files and chunks collections.
@ -31,10 +34,19 @@ use stdClass;
*/
class CollectionWrapper
{
/** @var string */
private $bucketName;
/** @var Collection */
private $chunksCollection;
/** @var string */
private $databaseName;
/** @var boolean */
private $checkedIndexes = false;
/** @var Collection */
private $filesCollection;
/**
@ -121,7 +133,7 @@ class CollectionWrapper
*
* @see Bucket::downloadToStreamByName()
* @see Bucket::openDownloadStreamByName()
* @param string $filename
* @param string $filename
* @param integer $revision
* @return stdClass|null
*/
@ -234,7 +246,7 @@ class CollectionWrapper
*/
public function insertChunk($chunk)
{
if ( ! $this->checkedIndexes) {
if (! $this->checkedIndexes) {
$this->ensureIndexes();
}
@ -250,7 +262,7 @@ class CollectionWrapper
*/
public function insertFile($file)
{
if ( ! $this->checkedIndexes) {
if (! $this->checkedIndexes) {
$this->ensureIndexes();
}
@ -261,7 +273,7 @@ class CollectionWrapper
* Updates the filename field in the file document for a given ID.
*
* @param mixed $id
* @param string $filename
* @param string $filename
* @return UpdateResult
*/
public function updateFilenameForId($id, $filename)
@ -314,7 +326,7 @@ class CollectionWrapper
$this->checkedIndexes = true;
if ( ! $this->isFilesCollectionEmpty()) {
if (! $this->isFilesCollectionEmpty()) {
return;
}

View File

@ -18,6 +18,7 @@
namespace MongoDB\GridFS\Exception;
use MongoDB\Exception\RuntimeException;
use function sprintf;
class CorruptFileException extends RuntimeException
{

View File

@ -18,6 +18,9 @@
namespace MongoDB\GridFS\Exception;
use MongoDB\Exception\RuntimeException;
use function MongoDB\BSON\fromPHP;
use function MongoDB\BSON\toJSON;
use function sprintf;
class FileNotFoundException extends RuntimeException
{
@ -43,7 +46,7 @@ class FileNotFoundException extends RuntimeException
*/
public static function byId($id, $namespace)
{
$json = \MongoDB\BSON\toJSON(\MongoDB\BSON\fromPHP(['_id' => $id]));
$json = toJSON(fromPHP(['_id' => $id]));
return new static(sprintf('File "%s" not found in "%s"', $json, $namespace));
}

View File

@ -17,10 +17,17 @@
namespace MongoDB\GridFS;
use IteratorIterator;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\GridFS\Exception\CorruptFileException;
use IteratorIterator;
use stdClass;
use function ceil;
use function floor;
use function is_integer;
use function property_exists;
use function sprintf;
use function strlen;
use function substr;
/**
* ReadableStream abstracts the process of reading a GridFS file.
@ -29,15 +36,34 @@ use stdClass;
*/
class ReadableStream
{
/** @var string|null */
private $buffer;
/** @var integer */
private $bufferOffset = 0;
/** @var integer */
private $chunkSize;
/** @var integer */
private $chunkOffset = 0;
/** @var IteratorIterator|null */
private $chunksIterator;
/** @var CollectionWrapper */
private $collectionWrapper;
/** @var float|integer */
private $expectedLastChunkSize = 0;
/** @var stdClass */
private $file;
/** @var integer */
private $length;
/** @var integer */
private $numChunks = 0;
/**
@ -49,15 +75,15 @@ class ReadableStream
*/
public function __construct(CollectionWrapper $collectionWrapper, stdClass $file)
{
if ( ! isset($file->chunkSize) || ! is_integer($file->chunkSize) || $file->chunkSize < 1) {
if (! isset($file->chunkSize) || ! is_integer($file->chunkSize) || $file->chunkSize < 1) {
throw new CorruptFileException('file.chunkSize is not an integer >= 1');
}
if ( ! isset($file->length) || ! is_integer($file->length) || $file->length < 0) {
if (! isset($file->length) || ! is_integer($file->length) || $file->length < 0) {
throw new CorruptFileException('file.length is not an integer > 0');
}
if ( ! isset($file->_id) && ! property_exists($file, '_id')) {
if (! isset($file->_id) && ! property_exists($file, '_id')) {
throw new CorruptFileException('file._id does not exist');
}
@ -202,6 +228,7 @@ class ReadableStream
*/
if ($lastChunkOffset > $this->chunkOffset) {
$this->chunksIterator = null;
return;
}
@ -239,7 +266,7 @@ class ReadableStream
return false;
}
if ( ! $this->chunksIterator->valid()) {
if (! $this->chunksIterator->valid()) {
throw CorruptFileException::missingChunk($this->chunkOffset);
}
@ -253,7 +280,7 @@ class ReadableStream
$actualChunkSize = strlen($this->buffer);
$expectedChunkSize = ($this->chunkOffset === $this->numChunks - 1)
$expectedChunkSize = $this->chunkOffset === $this->numChunks - 1
? $this->expectedLastChunkSize
: $this->chunkSize;

View File

@ -17,9 +17,24 @@
namespace MongoDB\GridFS;
use MongoDB\BSON\UTCDateTime;
use Exception;
use MongoDB\BSON\UTCDateTime;
use stdClass;
use function explode;
use function get_class;
use function in_array;
use function is_integer;
use function sprintf;
use function stream_context_get_options;
use function stream_get_wrappers;
use function stream_wrapper_register;
use function stream_wrapper_unregister;
use function trigger_error;
use const E_USER_WARNING;
use const SEEK_CUR;
use const SEEK_END;
use const SEEK_SET;
use const STREAM_IS_URL;
/**
* Stream wrapper for reading and writing a GridFS file.
@ -30,13 +45,16 @@ use stdClass;
*/
class StreamWrapper
{
/**
* @var resource|null Stream context (set by PHP)
*/
/** @var resource|null Stream context (set by PHP) */
public $context;
/** @var string|null */
private $mode;
/** @var string|null */
private $protocol;
/** @var ReadableStream|WritableStream|null */
private $stream;
/**
@ -60,7 +78,7 @@ class StreamWrapper
stream_wrapper_unregister($protocol);
}
stream_wrapper_register($protocol, get_called_class(), \STREAM_IS_URL);
stream_wrapper_register($protocol, static::class, STREAM_IS_URL);
}
/**
@ -81,7 +99,7 @@ class StreamWrapper
*/
public function stream_eof()
{
if ( ! $this->stream instanceof ReadableStream) {
if (! $this->stream instanceof ReadableStream) {
return false;
}
@ -96,6 +114,7 @@ class StreamWrapper
* @param string $mode Mode used to open the file (only "r" and "w" are supported)
* @param integer $options Additional flags set by the streams API
* @param string $openedPath Not used
* @return boolean
*/
public function stream_open($path, $mode, $options, &$openedPath)
{
@ -125,14 +144,15 @@ class StreamWrapper
*/
public function stream_read($length)
{
if ( ! $this->stream instanceof ReadableStream) {
if (! $this->stream instanceof ReadableStream) {
return '';
}
try {
return $this->stream->readBytes($length);
} catch (Exception $e) {
trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), \E_USER_WARNING);
trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), E_USER_WARNING);
return false;
}
}
@ -145,15 +165,15 @@ class StreamWrapper
* @param integer $whence One of SEEK_SET, SEEK_CUR, or SEEK_END
* @return boolean True if the position was updated and false otherwise
*/
public function stream_seek($offset, $whence = \SEEK_SET)
public function stream_seek($offset, $whence = SEEK_SET)
{
$size = $this->stream->getSize();
if ($whence === \SEEK_CUR) {
if ($whence === SEEK_CUR) {
$offset += $this->stream->tell();
}
if ($whence === \SEEK_END) {
if ($whence === SEEK_END) {
$offset += $size;
}
@ -221,14 +241,15 @@ class StreamWrapper
*/
public function stream_write($data)
{
if ( ! $this->stream instanceof WritableStream) {
if (! $this->stream instanceof WritableStream) {
return 0;
}
try {
return $this->stream->writeBytes($data);
} catch (Exception $e) {
trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), \E_USER_WARNING);
trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), E_USER_WARNING);
return false;
}
}
@ -241,6 +262,7 @@ class StreamWrapper
private function getStatTemplate()
{
return [
// phpcs:disable Squiz.Arrays.ArrayDeclaration.IndexNoNewline
0 => 0, 'dev' => 0,
1 => 0, 'ino' => 0,
2 => 0, 'mode' => 0,
@ -254,6 +276,7 @@ class StreamWrapper
10 => 0, 'ctime' => 0,
11 => -1, 'blksize' => -1,
12 => -1, 'blocks' => -1,
// phpcs:enable
];
}

View File

@ -23,6 +23,19 @@ use MongoDB\BSON\UTCDateTime;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use stdClass;
use function array_intersect_key;
use function hash_final;
use function hash_init;
use function hash_update;
use function is_array;
use function is_bool;
use function is_integer;
use function is_object;
use function is_string;
use function MongoDB\is_string_array;
use function sprintf;
use function strlen;
use function substr;
/**
* WritableStream abstracts the process of writing a GridFS file.
@ -31,16 +44,34 @@ use stdClass;
*/
class WritableStream
{
/** @var integer */
private static $defaultChunkSizeBytes = 261120;
/** @var string */
private $buffer = '';
/** @var integer */
private $chunkOffset = 0;
/** @var integer */
private $chunkSize;
/** @var boolean */
private $disableMD5;
/** @var CollectionWrapper */
private $collectionWrapper;
/** @var array */
private $file;
/** @var resource */
private $hashCtx;
/** @var boolean */
private $isClosed = false;
/** @var integer */
private $length = 0;
/**
@ -74,12 +105,12 @@ class WritableStream
public function __construct(CollectionWrapper $collectionWrapper, $filename, array $options = [])
{
$options += [
'_id' => new ObjectId,
'_id' => new ObjectId(),
'chunkSizeBytes' => self::$defaultChunkSizeBytes,
'disableMD5' => false,
];
if (isset($options['aliases']) && ! \MongoDB\is_string_array($options['aliases'])) {
if (isset($options['aliases']) && ! is_string_array($options['aliases'])) {
throw InvalidArgumentException::invalidType('"aliases" option', $options['aliases'], 'array of strings');
}
@ -107,7 +138,7 @@ class WritableStream
$this->collectionWrapper = $collectionWrapper;
$this->disableMD5 = $options['disableMD5'];
if ( ! $this->disableMD5) {
if (! $this->disableMD5) {
$this->hashCtx = hash_init('md5');
}
@ -233,9 +264,9 @@ class WritableStream
private function fileCollectionInsert()
{
$this->file['length'] = $this->length;
$this->file['uploadDate'] = new UTCDateTime;
$this->file['uploadDate'] = new UTCDateTime();
if ( ! $this->disableMD5) {
if (! $this->disableMD5) {
$this->file['md5'] = hash_final($this->hashCtx);
}
@ -265,7 +296,7 @@ class WritableStream
'data' => new Binary($data, Binary::TYPE_GENERIC),
];
if ( ! $this->disableMD5) {
if (! $this->disableMD5) {
hash_update($this->hashCtx, $data);
}

View File

@ -25,13 +25,16 @@ use MongoDB\Exception\BadMethodCallException;
*/
class InsertManyResult
{
/** @var WriteResult */
private $writeResult;
/** @var mixed[] */
private $insertedIds;
/** @var boolean */
private $isAcknowledged;
/**
* Constructor.
*
* @param WriteResult $writeResult
* @param mixed[] $insertedIds
*/

View File

@ -25,13 +25,16 @@ use MongoDB\Exception\BadMethodCallException;
*/
class InsertOneResult
{
/** @var WriteResult */
private $writeResult;
/** @var mixed */
private $insertedId;
/** @var boolean */
private $isAcknowledged;
/**
* Constructor.
*
* @param WriteResult $writeResult
* @param mixed $insertedId
*/

View File

@ -20,6 +20,7 @@ namespace MongoDB;
use IteratorAggregate;
use stdClass;
use Traversable;
use function call_user_func;
/**
* Result class for mapReduce command results.
@ -34,14 +35,19 @@ use Traversable;
*/
class MapReduceResult implements IteratorAggregate
{
/** @var callable */
private $getIterator;
/** @var integer */
private $executionTimeMS;
/** @var array */
private $counts;
/** @var array */
private $timing;
/**
* Constructor.
*
* @internal
* @param callable $getIterator Callback that returns a Traversable for mapReduce results
* @param stdClass $result Result document from the mapReduce command
@ -71,7 +77,7 @@ class MapReduceResult implements IteratorAggregate
*/
public function getExecutionTimeMS()
{
return $this->executionTimeMS;
return (integer) $this->executionTimeMS;
}
/**

View File

@ -17,10 +17,12 @@
namespace MongoDB\Model;
use MongoDB\BSON\Serializable;
use MongoDB\BSON\Unserializable;
use ArrayObject;
use JsonSerializable;
use MongoDB\BSON\Serializable;
use MongoDB\BSON\Unserializable;
use function array_values;
use function MongoDB\recursive_copy;
/**
* Model class for a BSON array.
@ -38,7 +40,7 @@ class BSONArray extends ArrayObject implements JsonSerializable, Serializable, U
public function __clone()
{
foreach ($this as $key => $value) {
$this[$key] = \MongoDB\recursive_copy($value);
$this[$key] = recursive_copy($value);
}
}
@ -52,7 +54,7 @@ class BSONArray extends ArrayObject implements JsonSerializable, Serializable, U
*/
public static function __set_state(array $properties)
{
$array = new static;
$array = new static();
$array->exchangeArray($properties);
return $array;

View File

@ -17,10 +17,11 @@
namespace MongoDB\Model;
use MongoDB\BSON\Serializable;
use MongoDB\BSON\Unserializable;
use ArrayObject;
use JsonSerializable;
use MongoDB\BSON\Serializable;
use MongoDB\BSON\Unserializable;
use function MongoDB\recursive_copy;
/**
* Model class for a BSON document.
@ -38,17 +39,18 @@ class BSONDocument extends ArrayObject implements JsonSerializable, Serializable
public function __clone()
{
foreach ($this as $key => $value) {
$this[$key] = \MongoDB\recursive_copy($value);
$this[$key] = recursive_copy($value);
}
}
/**
* Constructor.
*
* This overrides the parent constructor to allow property access of entries
* by default.
*
* @see http://php.net/arrayobject.construct
* @param array $input
* @param integer $flags
* @param string $iterator_class
*/
public function __construct($input = [], $flags = ArrayObject::ARRAY_AS_PROPS, $iterator_class = 'ArrayIterator')
{
@ -65,7 +67,7 @@ class BSONDocument extends ArrayObject implements JsonSerializable, Serializable
*/
public static function __set_state(array $properties)
{
$document = new static;
$document = new static();
$document->exchangeArray($properties);
return $document;

View File

@ -17,22 +17,40 @@
namespace MongoDB\Model;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Model\BSONDocument;
use Iterator;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use function is_array;
use function MongoDB\BSON\toPHP;
use function sprintf;
use function strlen;
use function substr;
use function unpack;
/**
* Iterator for BSON documents.
*/
class BSONIterator implements Iterator
{
/** @var integer */
private static $bsonSize = 4;
/** @var string */
private $buffer;
/** @var integer */
private $bufferLength;
/** @var mixed */
private $current;
/** @var integer */
private $key = 0;
/** @var integer */
private $position = 0;
/** @var array */
private $options;
/**
@ -54,7 +72,7 @@ class BSONIterator implements Iterator
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if ( ! isset($options['typeMap'])) {
if (! isset($options['typeMap'])) {
$options['typeMap'] = [];
}
@ -129,7 +147,7 @@ class BSONIterator implements Iterator
throw new UnexpectedValueException(sprintf('Expected %d bytes; %d remaining', $documentLength, $this->bufferLength - $this->position));
}
$this->current = \MongoDB\BSON\toPHP(substr($this->buffer, $this->position, $documentLength), $this->options['typeMap']);
$this->current = toPHP(substr($this->buffer, $this->position, $documentLength), $this->options['typeMap']);
$this->position += $documentLength;
}
}

View File

@ -21,6 +21,11 @@ use Countable;
use Generator;
use Iterator;
use Traversable;
use function count;
use function current;
use function key;
use function next;
use function reset;
/**
* Iterator for wrapping a Traversable and caching its results.
@ -33,14 +38,19 @@ use Traversable;
*/
class CachingIterator implements Countable, Iterator
{
/** @var array */
private $items = [];
/** @var Generator */
private $iterator;
/** @var boolean */
private $iteratorAdvanced = false;
/** @var boolean */
private $iteratorExhausted = false;
/**
* Constructor.
*
* Initialize the iterator and stores the first item in the cache. This
* effectively rewinds the Traversable and the wrapping Generator, which
* will execute up to its first yield statement. Additionally, this mimics
@ -90,7 +100,7 @@ class CachingIterator implements Countable, Iterator
*/
public function next()
{
if ( ! $this->iteratorExhausted) {
if (! $this->iteratorExhausted) {
$this->iterator->next();
$this->storeCurrentItem();
}
@ -128,7 +138,7 @@ class CachingIterator implements Countable, Iterator
*/
private function exhaustIterator()
{
while ( ! $this->iteratorExhausted) {
while (! $this->iteratorExhausted) {
$this->next();
}
}

View File

@ -0,0 +1,292 @@
<?php
/*
* Copyright 2019 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
use IteratorIterator;
use MongoDB\BSON\Serializable;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Monitoring\CommandFailedEvent;
use MongoDB\Driver\Monitoring\CommandStartedEvent;
use MongoDB\Driver\Monitoring\CommandSubscriber;
use MongoDB\Driver\Monitoring\CommandSucceededEvent;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\ResumeTokenException;
use MongoDB\Exception\UnexpectedValueException;
use function count;
use function is_array;
use function is_integer;
use function is_object;
use function MongoDB\Driver\Monitoring\addSubscriber;
use function MongoDB\Driver\Monitoring\removeSubscriber;
/**
* ChangeStreamIterator wraps a change stream's tailable cursor.
*
* This iterator tracks the size of each batch in order to determine when the
* postBatchResumeToken is applicable. It also ensures that initial calls to
* rewind() do not execute getMore commands.
*
* @internal
*/
class ChangeStreamIterator extends IteratorIterator implements CommandSubscriber
{
/** @var integer */
private $batchPosition = 0;
/** @var integer */
private $batchSize;
/** @var boolean */
private $isRewindNop;
/** @var boolean */
private $isValid = false;
/** @var object|null */
private $postBatchResumeToken;
/** @var array|object|null */
private $resumeToken;
/**
* @internal
* @param Cursor $cursor
* @param integer $firstBatchSize
* @param array|object|null $initialResumeToken
* @param object|null $postBatchResumeToken
*/
public function __construct(Cursor $cursor, $firstBatchSize, $initialResumeToken, $postBatchResumeToken)
{
if (! is_integer($firstBatchSize)) {
throw InvalidArgumentException::invalidType('$firstBatchSize', $firstBatchSize, 'integer');
}
if (isset($initialResumeToken) && ! is_array($initialResumeToken) && ! is_object($initialResumeToken)) {
throw InvalidArgumentException::invalidType('$initialResumeToken', $initialResumeToken, 'array or object');
}
if (isset($postBatchResumeToken) && ! is_object($postBatchResumeToken)) {
throw InvalidArgumentException::invalidType('$postBatchResumeToken', $postBatchResumeToken, 'object');
}
parent::__construct($cursor);
$this->batchSize = $firstBatchSize;
$this->isRewindNop = ($firstBatchSize === 0);
$this->postBatchResumeToken = $postBatchResumeToken;
$this->resumeToken = $initialResumeToken;
}
/** @internal */
final public function commandFailed(CommandFailedEvent $event)
{
}
/** @internal */
final public function commandStarted(CommandStartedEvent $event)
{
if ($event->getCommandName() !== 'getMore') {
return;
}
$this->batchPosition = 0;
$this->batchSize = null;
$this->postBatchResumeToken = null;
}
/** @internal */
final public function commandSucceeded(CommandSucceededEvent $event)
{
if ($event->getCommandName() !== 'getMore') {
return;
}
$reply = $event->getReply();
if (! isset($reply->cursor->nextBatch) || ! is_array($reply->cursor->nextBatch)) {
throw new UnexpectedValueException('getMore command did not return a "cursor.nextBatch" array');
}
$this->batchSize = count($reply->cursor->nextBatch);
if (isset($reply->cursor->postBatchResumeToken) && is_object($reply->cursor->postBatchResumeToken)) {
$this->postBatchResumeToken = $reply->cursor->postBatchResumeToken;
}
}
/**
* @see https://php.net/iteratoriterator.current
* @return mixed
*/
public function current()
{
return $this->isValid ? parent::current() : null;
}
/**
* Returns the resume token for the iterator's current position.
*
* Null may be returned if no change documents have been iterated and the
* server did not include a postBatchResumeToken in its aggregate or getMore
* command response.
*
* @return array|object|null
*/
public function getResumeToken()
{
return $this->resumeToken;
}
/**
* @see https://php.net/iteratoriterator.key
* @return mixed
*/
public function key()
{
return $this->isValid ? parent::key() : null;
}
/**
* @see https://php.net/iteratoriterator.rewind
* @return void
*/
public function next()
{
/* Determine if advancing the iterator will execute a getMore command
* (i.e. we are already positioned at the end of the current batch). If
* so, rely on the APM callbacks to reset $batchPosition and update
* $batchSize. Otherwise, we can forgo APM and manually increment
* $batchPosition after calling next(). */
$getMore = $this->isAtEndOfBatch();
if ($getMore) {
addSubscriber($this);
}
try {
parent::next();
$this->onIteration(! $getMore);
} finally {
if ($getMore) {
removeSubscriber($this);
}
}
}
/**
* @see https://php.net/iteratoriterator.rewind
* @return void
*/
public function rewind()
{
if ($this->isRewindNop) {
return;
}
parent::rewind();
$this->onIteration(false);
}
/**
* @see https://php.net/iteratoriterator.valid
* @return boolean
*/
public function valid()
{
return $this->isValid;
}
/**
* Extracts the resume token (i.e. "_id" field) from a change document.
*
* @param array|object $document Change document
* @return array|object
* @throws InvalidArgumentException
* @throws ResumeTokenException if the resume token is not found or invalid
*/
private function extractResumeToken($document)
{
if (! is_array($document) && ! is_object($document)) {
throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
}
if ($document instanceof Serializable) {
return $this->extractResumeToken($document->bsonSerialize());
}
$resumeToken = is_array($document)
? (isset($document['_id']) ? $document['_id'] : null)
: (isset($document->_id) ? $document->_id : null);
if (! isset($resumeToken)) {
$this->isValid = false;
throw ResumeTokenException::notFound();
}
if (! is_array($resumeToken) && ! is_object($resumeToken)) {
$this->isValid = false;
throw ResumeTokenException::invalidType($resumeToken);
}
return $resumeToken;
}
/**
* Return whether the iterator is positioned at the end of the batch.
*
* @return boolean
*/
private function isAtEndOfBatch()
{
return $this->batchPosition + 1 >= $this->batchSize;
}
/**
* Perform housekeeping after an iteration event.
*
* @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#updating-the-cached-resume-token
* @param boolean $incrementBatchPosition
*/
private function onIteration($incrementBatchPosition)
{
$this->isValid = parent::valid();
/* Disable rewind()'s NOP behavior once we advance to a valid position.
* This will allow the driver to throw a LogicException if rewind() is
* called after the cursor has advanced past its first element. */
if ($this->isRewindNop && $this->isValid) {
$this->isRewindNop = false;
}
if ($incrementBatchPosition && $this->isValid) {
$this->batchPosition++;
}
/* If the iterator is positioned at the end of the batch, apply the
* postBatchResumeToken if it's available. This handles both the case
* where the current batch is empty (since onIteration() will be called
* after a successful getMore) and when the iterator has advanced to the
* last document in its current batch. Otherwise, extract a resume token
* from the current document if possible. */
if ($this->isAtEndOfBatch() && $this->postBatchResumeToken !== null) {
$this->resumeToken = $this->postBatchResumeToken;
} elseif ($this->isValid) {
$this->resumeToken = $this->extractResumeToken($this->current());
}
}
}

View File

@ -17,8 +17,9 @@
namespace MongoDB\Model;
use MongoDB\Exception\BadMethodCallException;
use ArrayAccess;
use MongoDB\Exception\BadMethodCallException;
use function array_key_exists;
/**
* Collection information model class.
@ -33,11 +34,10 @@ use ArrayAccess;
*/
class CollectionInfo implements ArrayAccess
{
/** @var array */
private $info;
/**
* Constructor.
*
* @param array $info Collection info
*/
public function __construct(array $info)
@ -63,6 +63,7 @@ class CollectionInfo implements ArrayAccess
*/
public function getCappedMax()
{
/* The MongoDB server might return this number as an integer or float */
return isset($this->info['options']['max']) ? (integer) $this->info['options']['max'] : null;
}
@ -73,6 +74,7 @@ class CollectionInfo implements ArrayAccess
*/
public function getCappedSize()
{
/* The MongoDB server might return this number as an integer or float */
return isset($this->info['options']['size']) ? (integer) $this->info['options']['size'] : null;
}
@ -134,21 +136,24 @@ class CollectionInfo implements ArrayAccess
* Not supported.
*
* @see http://php.net/arrayaccess.offsetset
* @param mixed $key
* @param mixed $value
* @throws BadMethodCallException
*/
public function offsetSet($key, $value)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
throw BadMethodCallException::classIsImmutable(self::class);
}
/**
* Not supported.
*
* @see http://php.net/arrayaccess.offsetunset
* @param mixed $key
* @throws BadMethodCallException
*/
public function offsetUnset($key)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
throw BadMethodCallException::classIsImmutable(self::class);
}
}

View File

@ -17,8 +17,10 @@
namespace MongoDB\Model;
use MongoDB\Exception\BadMethodCallException;
use ArrayAccess;
use MongoDB\Exception\BadMethodCallException;
use function array_key_exists;
/**
* Database information model class.
*
@ -31,11 +33,10 @@ use ArrayAccess;
*/
class DatabaseInfo implements ArrayAccess
{
/** @var array */
private $info;
/**
* Constructor.
*
* @param array $info Database info
*/
public function __construct(array $info)
@ -71,6 +72,7 @@ class DatabaseInfo implements ArrayAccess
*/
public function getSizeOnDisk()
{
/* The MongoDB server might return this number as an integer or float */
return (integer) $this->info['sizeOnDisk'];
}
@ -112,21 +114,24 @@ class DatabaseInfo implements ArrayAccess
* Not supported.
*
* @see http://php.net/arrayaccess.offsetset
* @param mixed $key
* @param mixed $value
* @throws BadMethodCallException
*/
public function offsetSet($key, $value)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
throw BadMethodCallException::classIsImmutable(self::class);
}
/**
* Not supported.
*
* @see http://php.net/arrayaccess.offsetunset
* @param mixed $key
* @throws BadMethodCallException
*/
public function offsetUnset($key)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
throw BadMethodCallException::classIsImmutable(self::class);
}
}

View File

@ -17,6 +17,11 @@
namespace MongoDB\Model;
use function current;
use function key;
use function next;
use function reset;
/**
* DatabaseInfoIterator for inline listDatabases command results.
*
@ -29,11 +34,10 @@ namespace MongoDB\Model;
*/
class DatabaseInfoLegacyIterator implements DatabaseInfoIterator
{
/** @var array */
private $databases;
/**
* Constructor.
*
* @param array $databases
*/
public function __construct(array $databases)

View File

@ -17,8 +17,10 @@
namespace MongoDB\Model;
use MongoDB\Exception\BadMethodCallException;
use ArrayAccess;
use MongoDB\Exception\BadMethodCallException;
use function array_key_exists;
use function array_search;
/**
* Index information model class.
@ -37,11 +39,10 @@ use ArrayAccess;
*/
class IndexInfo implements ArrayAccess
{
/** @var array */
private $info;
/**
* Constructor.
*
* @param array $info Index info
*/
public function __construct(array $info)
@ -206,21 +207,24 @@ class IndexInfo implements ArrayAccess
* Not supported.
*
* @see http://php.net/arrayaccess.offsetset
* @param mixed $key
* @param mixed $value
* @throws BadMethodCallException
*/
public function offsetSet($key, $value)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
throw BadMethodCallException::classIsImmutable(self::class);
}
/**
* Not supported.
*
* @see http://php.net/arrayaccess.offsetunset
* @param mixed $key
* @throws BadMethodCallException
*/
public function offsetUnset($key)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
throw BadMethodCallException::classIsImmutable(self::class);
}
}

View File

@ -19,6 +19,13 @@ namespace MongoDB\Model;
use MongoDB\BSON\Serializable;
use MongoDB\Exception\InvalidArgumentException;
use function is_array;
use function is_float;
use function is_int;
use function is_object;
use function is_string;
use function MongoDB\generate_index_name;
use function sprintf;
/**
* Index input model class.
@ -32,43 +39,42 @@ use MongoDB\Exception\InvalidArgumentException;
*/
class IndexInput implements Serializable
{
/** @var array */
private $index;
/**
* Constructor.
*
* @param array $index Index specification
* @throws InvalidArgumentException
*/
public function __construct(array $index)
{
if ( ! isset($index['key'])) {
if (! isset($index['key'])) {
throw new InvalidArgumentException('Required "key" document is missing from index specification');
}
if ( ! is_array($index['key']) && ! is_object($index['key'])) {
if (! is_array($index['key']) && ! is_object($index['key'])) {
throw InvalidArgumentException::invalidType('"key" option', $index['key'], 'array or object');
}
foreach ($index['key'] as $fieldName => $order) {
if ( ! is_int($order) && ! is_float($order) && ! is_string($order)) {
if (! is_int($order) && ! is_float($order) && ! is_string($order)) {
throw InvalidArgumentException::invalidType(sprintf('order value for "%s" field within "key" option', $fieldName), $order, 'numeric or string');
}
}
if ( ! isset($index['ns'])) {
if (! isset($index['ns'])) {
throw new InvalidArgumentException('Required "ns" option is missing from index specification');
}
if ( ! is_string($index['ns'])) {
if (! is_string($index['ns'])) {
throw InvalidArgumentException::invalidType('"ns" option', $index['ns'], 'string');
}
if ( ! isset($index['name'])) {
$index['name'] = \MongoDB\generate_index_name($index['key']);
if (! isset($index['name'])) {
$index['name'] = generate_index_name($index['key']);
}
if ( ! is_string($index['name'])) {
if (! is_string($index['name'])) {
throw InvalidArgumentException::invalidType('"name" option', $index['name'], 'string');
}
@ -78,7 +84,7 @@ class IndexInput implements Serializable
/**
* Return the index name.
*
* @param string
* @return string
*/
public function __toString()
{

View File

@ -1,166 +0,0 @@
<?php
/*
* Copyright 2016-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
use ArrayIterator;
use MongoDB\Exception\BadMethodCallException;
/**
* Iterator for applying a type map to documents in inline command results.
*
* @internal
*/
class TypeMapArrayIterator extends ArrayIterator
{
private $typeMap;
/**
* Constructor.
*
* @param array $documents
* @param array $typeMap
*/
public function __construct(array $documents = [], array $typeMap)
{
parent::__construct($documents);
$this->typeMap = $typeMap;
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.append
* @throws BadMethodCallException
*/
public function append($value)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.asort
* @throws BadMethodCallException
*/
public function asort()
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Return the current element with the type map applied to it.
*
* @see http://php.net/arrayiterator.current
* @return array|object
*/
public function current()
{
return \MongoDB\apply_type_map_to_document(parent::current(), $this->typeMap);
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.ksort
* @throws BadMethodCallException
*/
public function ksort()
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.natcasesort
* @throws BadMethodCallException
*/
public function natcasesort()
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.natsort
* @throws BadMethodCallException
*/
public function natsort()
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Return the value from the provided offset with the type map applied.
*
* @see http://php.net/arrayiterator.offsetget
* @param mixed $offset
* @return array|object
*/
public function offsetGet($offset)
{
return \MongoDB\apply_type_map_to_document(parent::offsetGet($offset), $this->typeMap);
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.offsetset
* @throws BadMethodCallException
*/
public function offsetSet($index, $newval)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.offsetunset
* @throws BadMethodCallException
*/
public function offsetUnset($index)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.uasort
* @throws BadMethodCallException
*/
public function uasort($cmp_function)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.uksort
* @throws BadMethodCallException
*/
public function uksort($cmp_function)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
}

View File

@ -17,20 +17,29 @@
namespace MongoDB\Operation;
use ArrayIterator;
use MongoDB\Driver\Command;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Model\TypeMapArrayIterator;
use ArrayIterator;
use stdClass;
use Traversable;
use function current;
use function is_array;
use function is_bool;
use function is_integer;
use function is_object;
use function is_string;
use function MongoDB\create_field_path_type_map;
use function MongoDB\is_last_pipeline_operator_write;
use function MongoDB\server_supports_feature;
use function sprintf;
/**
* Operation for the aggregate command.
@ -41,14 +50,28 @@ use Traversable;
*/
class Aggregate implements Executable
{
/** @var integer */
private static $wireVersionForCollation = 5;
/** @var integer */
private static $wireVersionForDocumentLevelValidation = 4;
/** @var integer */
private static $wireVersionForReadConcern = 4;
/** @var integer */
private static $wireVersionForWriteConcern = 5;
/** @var string */
private $databaseName;
/** @var string|null */
private $collectionName;
/** @var array */
private $pipeline;
/** @var array */
private $options;
/**
@ -63,8 +86,8 @@ class Aggregate implements Executable
* * batchSize (integer): The number of documents to return per batch.
*
* * bypassDocumentValidation (boolean): If true, allows the write to
* circumvent document level validation. This only applies when the $out
* stage is specified.
* circumvent document level validation. This only applies when an $out
* or $merge stage is specified.
*
* For servers < 3.2, this option is ignored as document level validation
* is not available.
@ -87,15 +110,14 @@ class Aggregate implements Executable
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * readConcern (MongoDB\Driver\ReadConcern): Read concern. Note that a
* "majority" read concern is not compatible with the $out stage.
* * readConcern (MongoDB\Driver\ReadConcern): Read concern.
*
* This is not supported for server versions < 3.2 and will result in an
* exception at execution time if used.
*
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
*
* This option is ignored if the $out stage is specified.
* This option is ignored if an $out or $merge stage is specified.
*
* * session (MongoDB\Driver\Session): Client session.
*
@ -111,7 +133,7 @@ class Aggregate implements Executable
* mongod/mongos upgrades.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern. This only
* applies when the $out stage is specified.
* applies when an $out or $merge stage is specified.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
@ -134,7 +156,7 @@ class Aggregate implements Executable
throw new InvalidArgumentException(sprintf('$pipeline is not a list (unexpected index: "%s")', $i));
}
if ( ! is_array($operation) && ! is_object($operation)) {
if (! is_array($operation) && ! is_object($operation)) {
throw InvalidArgumentException::invalidType(sprintf('$pipeline[%d]', $i), $operation, 'array or object');
}
@ -146,7 +168,7 @@ class Aggregate implements Executable
'useCursor' => true,
];
if ( ! is_bool($options['allowDiskUse'])) {
if (! is_bool($options['allowDiskUse'])) {
throw InvalidArgumentException::invalidType('"allowDiskUse" option', $options['allowDiskUse'], 'boolean');
}
@ -183,27 +205,27 @@ class Aggregate implements Executable
}
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if ( ! is_bool($options['useCursor'])) {
if (! is_bool($options['useCursor'])) {
throw InvalidArgumentException::invalidType('"useCursor" option', $options['useCursor'], 'boolean');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}
if (isset($options['batchSize']) && ! $options['useCursor']) {
@ -218,7 +240,7 @@ class Aggregate implements Executable
unset($options['writeConcern']);
}
if ( ! empty($options['explain'])) {
if (! empty($options['explain'])) {
$options['useCursor'] = false;
}
@ -240,25 +262,35 @@ class Aggregate implements Executable
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
if (isset($this->options['readConcern']) && ! server_supports_feature($server, self::$wireVersionForReadConcern)) {
throw UnsupportedException::readConcernNotSupported();
}
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction) {
if (isset($this->options['readConcern'])) {
throw UnsupportedException::readConcernNotSupportedInTransaction();
}
if (isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
}
$hasExplain = ! empty($this->options['explain']);
$hasOutStage = \MongoDB\is_last_pipeline_operator_out($this->pipeline);
$hasWriteStage = is_last_pipeline_operator_write($this->pipeline);
$command = $this->createCommand($server);
$options = $this->createOptions($hasOutStage, $hasExplain);
$command = $this->createCommand($server, $hasWriteStage);
$options = $this->createOptions($hasWriteStage, $hasExplain);
$cursor = ($hasOutStage && ! $hasExplain)
$cursor = $hasWriteStage && ! $hasExplain
? $server->executeReadWriteCommand($this->databaseName, $command, $options)
: $server->executeReadCommand($this->databaseName, $command, $options);
@ -270,14 +302,14 @@ class Aggregate implements Executable
return $cursor;
}
$result = current($cursor->toArray());
if ( ! isset($result->result) || ! is_array($result->result)) {
throw new UnexpectedValueException('aggregate command did not return a "result" array');
if (isset($this->options['typeMap'])) {
$cursor->setTypeMap(create_field_path_type_map($this->options['typeMap'], 'result.$'));
}
if (isset($this->options['typeMap'])) {
return new TypeMapArrayIterator($result->result, $this->options['typeMap']);
$result = current($cursor->toArray());
if (! isset($result->result) || ! is_array($result->result)) {
throw new UnexpectedValueException('aggregate command did not return a "result" array');
}
return new ArrayIterator($result->result);
@ -287,9 +319,10 @@ class Aggregate implements Executable
* Create the aggregate command.
*
* @param Server $server
* @param boolean $hasWriteStage
* @return Command
*/
private function createCommand(Server $server)
private function createCommand(Server $server, $hasWriteStage)
{
$cmd = [
'aggregate' => isset($this->collectionName) ? $this->collectionName : 1,
@ -299,7 +332,9 @@ class Aggregate implements Executable
$cmd['allowDiskUse'] = $this->options['allowDiskUse'];
if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
if (! empty($this->options['bypassDocumentValidation']) &&
server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)
) {
$cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
}
@ -322,9 +357,12 @@ class Aggregate implements Executable
}
if ($this->options['useCursor']) {
$cmd['cursor'] = isset($this->options["batchSize"])
/* Ignore batchSize if pipeline includes an $out or $merge stage, as
* no documents will be returned and sending a batchSize of zero
* could prevent the pipeline from executing at all. */
$cmd['cursor'] = isset($this->options["batchSize"]) && ! $hasWriteStage
? ['batchSize' => $this->options["batchSize"]]
: new stdClass;
: new stdClass();
}
return new Command($cmd, $cmdOptions);
@ -335,10 +373,11 @@ class Aggregate implements Executable
*
* @see http://php.net/manual/en/mongodb-driver-server.executereadcommand.php
* @see http://php.net/manual/en/mongodb-driver-server.executereadwritecommand.php
* @param boolean $hasOutStage
* @param boolean $hasWriteStage
* @param boolean $hasExplain
* @return array
*/
private function createOptions($hasOutStage, $hasExplain)
private function createOptions($hasWriteStage, $hasExplain)
{
$options = [];
@ -346,7 +385,7 @@ class Aggregate implements Executable
$options['readConcern'] = $this->options['readConcern'];
}
if ( ! $hasOutStage && isset($this->options['readPreference'])) {
if (! $hasWriteStage && isset($this->options['readPreference'])) {
$options['readPreference'] = $this->options['readPreference'];
}
@ -354,7 +393,7 @@ class Aggregate implements Executable
$options['session'] = $this->options['session'];
}
if ($hasOutStage && ! $hasExplain && isset($this->options['writeConcern'])) {
if ($hasWriteStage && ! $hasExplain && isset($this->options['writeConcern'])) {
$options['writeConcern'] = $this->options['writeConcern'];
}

View File

@ -19,12 +19,23 @@ namespace MongoDB\Operation;
use MongoDB\BulkWriteResult;
use MongoDB\Driver\BulkWrite as Bulk;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use function array_key_exists;
use function count;
use function current;
use function is_array;
use function is_bool;
use function is_object;
use function key;
use function MongoDB\is_first_key_operator;
use function MongoDB\is_pipeline;
use function MongoDB\server_supports_feature;
use function sprintf;
/**
* Operation for executing multiple write operations.
@ -41,15 +52,31 @@ class BulkWrite implements Executable
const UPDATE_MANY = 'updateMany';
const UPDATE_ONE = 'updateOne';
/** @var integer */
private static $wireVersionForArrayFilters = 6;
/** @var integer */
private static $wireVersionForCollation = 5;
/** @var integer */
private static $wireVersionForDocumentLevelValidation = 4;
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var array[] */
private $operations;
/** @var array */
private $options;
/** @var boolean */
private $isArrayFiltersUsed = false;
/** @var boolean */
private $isCollationUsed = false;
/**
@ -132,7 +159,7 @@ class BulkWrite implements Executable
throw new InvalidArgumentException(sprintf('$operations is not a list (unexpected index: "%s")', $i));
}
if ( ! is_array($operation)) {
if (! is_array($operation)) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]', $i), $operation, 'array');
}
@ -143,11 +170,11 @@ class BulkWrite implements Executable
$type = key($operation);
$args = current($operation);
if ( ! isset($args[0]) && ! array_key_exists(0, $args)) {
if (! isset($args[0]) && ! array_key_exists(0, $args)) {
throw new InvalidArgumentException(sprintf('Missing first argument for $operations[%d]["%s"]', $i, $type));
}
if ( ! is_array($args[0]) && ! is_object($args[0])) {
if (! is_array($args[0]) && ! is_object($args[0])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][0]', $i, $type), $args[0], 'array or object');
}
@ -157,11 +184,11 @@ class BulkWrite implements Executable
case self::DELETE_MANY:
case self::DELETE_ONE:
if ( ! isset($args[1])) {
if (! isset($args[1])) {
$args[1] = [];
}
if ( ! is_array($args[1])) {
if (! is_array($args[1])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array');
}
@ -170,7 +197,7 @@ class BulkWrite implements Executable
if (isset($args[1]['collation'])) {
$this->isCollationUsed = true;
if ( ! is_array($args[1]['collation']) && ! is_object($args[1]['collation'])) {
if (! is_array($args[1]['collation']) && ! is_object($args[1]['collation'])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]["collation"]', $i, $type), $args[1]['collation'], 'array or object');
}
}
@ -180,23 +207,23 @@ class BulkWrite implements Executable
break;
case self::REPLACE_ONE:
if ( ! isset($args[1]) && ! array_key_exists(1, $args)) {
if (! isset($args[1]) && ! array_key_exists(1, $args)) {
throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type));
}
if ( ! is_array($args[1]) && ! is_object($args[1])) {
if (! is_array($args[1]) && ! is_object($args[1])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array or object');
}
if (\MongoDB\is_first_key_operator($args[1])) {
if (is_first_key_operator($args[1])) {
throw new InvalidArgumentException(sprintf('First key in $operations[%d]["%s"][1] is an update operator', $i, $type));
}
if ( ! isset($args[2])) {
if (! isset($args[2])) {
$args[2] = [];
}
if ( ! is_array($args[2])) {
if (! is_array($args[2])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]', $i, $type), $args[2], 'array');
}
@ -206,12 +233,12 @@ class BulkWrite implements Executable
if (isset($args[2]['collation'])) {
$this->isCollationUsed = true;
if ( ! is_array($args[2]['collation']) && ! is_object($args[2]['collation'])) {
if (! is_array($args[2]['collation']) && ! is_object($args[2]['collation'])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["collation"]', $i, $type), $args[2]['collation'], 'array or object');
}
}
if ( ! is_bool($args[2]['upsert'])) {
if (! is_bool($args[2]['upsert'])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean');
}
@ -221,23 +248,23 @@ class BulkWrite implements Executable
case self::UPDATE_MANY:
case self::UPDATE_ONE:
if ( ! isset($args[1]) && ! array_key_exists(1, $args)) {
if (! isset($args[1]) && ! array_key_exists(1, $args)) {
throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type));
}
if ( ! is_array($args[1]) && ! is_object($args[1])) {
if (! is_array($args[1]) && ! is_object($args[1])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array or object');
}
if ( ! \MongoDB\is_first_key_operator($args[1])) {
throw new InvalidArgumentException(sprintf('First key in $operations[%d]["%s"][1] is not an update operator', $i, $type));
if (! is_first_key_operator($args[1]) && ! is_pipeline($args[1])) {
throw new InvalidArgumentException(sprintf('First key in $operations[%d]["%s"][1] is neither an update operator nor a pipeline', $i, $type));
}
if ( ! isset($args[2])) {
if (! isset($args[2])) {
$args[2] = [];
}
if ( ! is_array($args[2])) {
if (! is_array($args[2])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]', $i, $type), $args[2], 'array');
}
@ -247,7 +274,7 @@ class BulkWrite implements Executable
if (isset($args[2]['arrayFilters'])) {
$this->isArrayFiltersUsed = true;
if ( ! is_array($args[2]['arrayFilters'])) {
if (! is_array($args[2]['arrayFilters'])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["arrayFilters"]', $i, $type), $args[2]['arrayFilters'], 'array');
}
}
@ -255,12 +282,12 @@ class BulkWrite implements Executable
if (isset($args[2]['collation'])) {
$this->isCollationUsed = true;
if ( ! is_array($args[2]['collation']) && ! is_object($args[2]['collation'])) {
if (! is_array($args[2]['collation']) && ! is_object($args[2]['collation'])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["collation"]', $i, $type), $args[2]['collation'], 'array or object');
}
}
if ( ! is_bool($args[2]['upsert'])) {
if (! is_bool($args[2]['upsert'])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean');
}
@ -281,16 +308,16 @@ class BulkWrite implements Executable
throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean');
}
if ( ! is_bool($options['ordered'])) {
if (! is_bool($options['ordered'])) {
throw InvalidArgumentException::invalidType('"ordered" option', $options['ordered'], 'boolean');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
@ -314,17 +341,24 @@ class BulkWrite implements Executable
*/
public function execute(Server $server)
{
if ($this->isArrayFiltersUsed && ! \MongoDB\server_supports_feature($server, self::$wireVersionForArrayFilters)) {
if ($this->isArrayFiltersUsed && ! server_supports_feature($server, self::$wireVersionForArrayFilters)) {
throw UnsupportedException::arrayFiltersNotSupported();
}
if ($this->isCollationUsed && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
if ($this->isCollationUsed && ! server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
$options = ['ordered' => $this->options['ordered']];
if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
if (! empty($this->options['bypassDocumentValidation']) &&
server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)
) {
$options['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
}

View File

@ -18,14 +18,21 @@
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
use function current;
use function is_array;
use function is_float;
use function is_integer;
use function is_object;
use function is_string;
use function MongoDB\server_supports_feature;
/**
* Operation for the count command.
@ -36,12 +43,22 @@ use MongoDB\Exception\UnsupportedException;
*/
class Count implements Executable, Explainable
{
/** @var integer */
private static $wireVersionForCollation = 5;
/** @var integer */
private static $wireVersionForReadConcern = 4;
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var array|object */
private $filter;
/** @var array */
private $options;
/**
@ -85,7 +102,7 @@ class Count implements Executable, Explainable
*/
public function __construct($databaseName, $collectionName, $filter = [], array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
if (! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}
@ -106,15 +123,15 @@ class Count implements Executable, Explainable
}
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['skip']) && ! is_integer($options['skip'])) {
@ -143,19 +160,24 @@ class Count implements Executable, Explainable
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
if (isset($this->options['readConcern']) && ! server_supports_feature($server, self::$wireVersionForReadConcern)) {
throw UnsupportedException::readConcernNotSupported();
}
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['readConcern'])) {
throw UnsupportedException::readConcernNotSupportedInTransaction();
}
$cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
$result = current($cursor->toArray());
// Older server versions may return a float
if ( ! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) {
if (! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) {
throw new UnexpectedValueException('count command did not return a numeric "n" value');
}
@ -176,7 +198,7 @@ class Count implements Executable, Explainable
{
$cmd = ['count' => $this->collectionName];
if ( ! empty($this->filter)) {
if (! empty($this->filter)) {
$cmd['query'] = (object) $this->filter;
}

View File

@ -17,15 +17,18 @@
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
use function array_intersect_key;
use function count;
use function current;
use function is_array;
use function is_float;
use function is_integer;
use function is_object;
/**
* Operation for obtaining an exact count of documents in a collection
@ -36,13 +39,23 @@ use MongoDB\Exception\UnsupportedException;
*/
class CountDocuments implements Executable
{
private static $wireVersionForCollation = 5;
private static $wireVersionForReadConcern = 4;
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var array|object */
private $filter;
private $options;
/** @var array */
private $aggregateOptions;
/** @var array */
private $countOptions;
/** @var Aggregate */
private $aggregate;
/**
* Constructs an aggregate command for counting documents
@ -85,50 +98,26 @@ class CountDocuments implements Executable
*/
public function __construct($databaseName, $collectionName, $filter, array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
if (! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}
if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
}
if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) {
throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], 'string or array or object');
}
if (isset($options['limit']) && ! is_integer($options['limit'])) {
throw InvalidArgumentException::invalidType('"limit" option', $options['limit'], 'integer');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['skip']) && ! is_integer($options['skip'])) {
throw InvalidArgumentException::invalidType('"skip" option', $options['skip'], 'integer');
}
if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
unset($options['readConcern']);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->filter = $filter;
$this->options = $options;
$this->aggregateOptions = array_intersect_key($options, ['collation' => 1, 'hint' => 1, 'maxTimeMS' => 1, 'readConcern' => 1, 'readPreference' => 1, 'session' => 1]);
$this->countOptions = array_intersect_key($options, ['limit' => 1, 'skip' => 1]);
$this->aggregate = $this->createAggregate();
}
/**
@ -143,15 +132,7 @@ class CountDocuments implements Executable
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
throw UnsupportedException::readConcernNotSupported();
}
$cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
$cursor = $this->aggregate->execute($server);
$allResults = $cursor->toArray();
/* If there are no documents to count, the aggregation pipeline has no items to group, and
@ -161,7 +142,7 @@ class CountDocuments implements Executable
}
$result = current($allResults);
if ( ! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) {
if (! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) {
throw new UnexpectedValueException('count command did not return a numeric "n" value');
}
@ -169,69 +150,24 @@ class CountDocuments implements Executable
}
/**
* Create the count command document.
*
* @return array
* @return Aggregate
*/
private function createCommandDocument()
private function createAggregate()
{
$pipeline = [
['$match' => (object) $this->filter]
['$match' => (object) $this->filter],
];
if (isset($this->options['skip'])) {
$pipeline[] = ['$skip' => $this->options['skip']];
if (isset($this->countOptions['skip'])) {
$pipeline[] = ['$skip' => $this->countOptions['skip']];
}
if (isset($this->options['limit'])) {
$pipeline[] = ['$limit' => $this->options['limit']];
if (isset($this->countOptions['limit'])) {
$pipeline[] = ['$limit' => $this->countOptions['limit']];
}
$pipeline[] = ['$group' => ['_id' => null, 'n' => ['$sum' => 1]]];
$pipeline[] = ['$group' => ['_id' => 1, 'n' => ['$sum' => 1]]];
$cmd = [
'aggregate' => $this->collectionName,
'pipeline' => $pipeline,
'cursor' => (object) [],
];
if (isset($this->options['collation'])) {
$cmd['collation'] = (object) $this->options['collation'];
}
if (isset($this->options['hint'])) {
$cmd['hint'] = is_array($this->options['hint']) ? (object) $this->options['hint'] : $this->options['hint'];
}
if (isset($this->options['maxTimeMS'])) {
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
return $cmd;
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executereadcommand.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['readConcern'])) {
$options['readConcern'] = $this->options['readConcern'];
}
if (isset($this->options['readPreference'])) {
$options['readPreference'] = $this->options['readPreference'];
}
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
return $options;
return new Aggregate($this->databaseName, $this->collectionName, $pipeline, $this->aggregateOptions);
}
}

View File

@ -18,12 +18,21 @@
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use function current;
use function is_array;
use function is_bool;
use function is_integer;
use function is_object;
use function is_string;
use function MongoDB\server_supports_feature;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* Operation for the create command.
@ -37,11 +46,19 @@ class CreateCollection implements Executable
const USE_POWER_OF_2_SIZES = 1;
const NO_PADDING = 2;
/** @var integer */
private static $wireVersionForCollation = 5;
/** @var integer */
private static $wireVersionForWriteConcern = 5;
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var array */
private $options = [];
/**
@ -139,7 +156,7 @@ class CreateCollection implements Executable
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['size']) && ! is_integer($options['size'])) {
@ -167,7 +184,7 @@ class CreateCollection implements Executable
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
@ -194,11 +211,11 @@ class CreateCollection implements Executable
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}

View File

@ -17,15 +17,19 @@
namespace MongoDB\Operation;
use MongoDB\Driver\BulkWrite as Bulk;
use MongoDB\Driver\Command;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Model\IndexInput;
use function array_map;
use function is_array;
use function is_integer;
use function MongoDB\server_supports_feature;
use function sprintf;
/**
* Operation for the createIndexes command.
@ -37,13 +41,25 @@ use MongoDB\Model\IndexInput;
*/
class CreateIndexes implements Executable
{
/** @var integer */
private static $wireVersionForCollation = 5;
/** @var integer */
private static $wireVersionForWriteConcern = 5;
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var array */
private $indexes = [];
/** @var boolean */
private $isCollationUsed = false;
/** @var array */
private $options = [];
/**
@ -82,11 +98,11 @@ class CreateIndexes implements Executable
throw new InvalidArgumentException(sprintf('$indexes is not a list (unexpected index: "%s")', $i));
}
if ( ! is_array($index)) {
if (! is_array($index)) {
throw InvalidArgumentException::invalidType(sprintf('$index[%d]', $i), $index, 'array');
}
if ( ! isset($index['ns'])) {
if (! isset($index['ns'])) {
$index['ns'] = $databaseName . '.' . $collectionName;
}
@ -99,16 +115,16 @@ class CreateIndexes implements Executable
$expectedIndex += 1;
}
if (isset($options['maxTimeMS']) && !is_integer($options['maxTimeMS'])) {
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
@ -131,17 +147,24 @@ class CreateIndexes implements Executable
*/
public function execute(Server $server)
{
if ($this->isCollationUsed && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
if ($this->isCollationUsed && ! server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
$this->executeCommand($server);
return array_map(function(IndexInput $index) { return (string) $index; }, $this->indexes);
return array_map(function (IndexInput $index) {
return (string) $index;
}, $this->indexes);
}
/**

View File

@ -23,6 +23,8 @@ use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Exception\InvalidArgumentException;
use function is_array;
use function is_object;
/**
* Operation for executing a database command.
@ -32,8 +34,13 @@ use MongoDB\Exception\InvalidArgumentException;
*/
class DatabaseCommand implements Executable
{
/** @var string */
private $databaseName;
/** @var array|Command|object */
private $command;
/** @var array */
private $options;
/**
@ -54,23 +61,23 @@ class DatabaseCommand implements Executable
* * typeMap (array): Type map for BSON deserialization. This will be
* applied to the returned Cursor (it is not sent to the server).
*
* @param string $databaseName Database name
* @param array|object $command Command document
* @param array $options Options for command execution
* @param string $databaseName Database name
* @param array|object $command Command document
* @param array $options Options for command execution
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $command, array $options = [])
{
if ( ! is_array($command) && ! is_object($command)) {
if (! is_array($command) && ! is_object($command)) {
throw InvalidArgumentException::invalidType('$command', $command, 'array or object');
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
@ -78,7 +85,7 @@ class DatabaseCommand implements Executable
}
$this->databaseName = (string) $databaseName;
$this->command = ($command instanceof Command) ? $command : new Command($command);
$this->command = $command instanceof Command ? $command : new Command($command);
$this->options = $options;
}

View File

@ -19,12 +19,15 @@ namespace MongoDB\Operation;
use MongoDB\DeleteResult;
use MongoDB\Driver\BulkWrite as Bulk;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use function is_array;
use function is_object;
use function MongoDB\server_supports_feature;
/**
* Operation for the delete command.
@ -37,12 +40,22 @@ use MongoDB\Exception\UnsupportedException;
*/
class Delete implements Executable, Explainable
{
/** @var integer */
private static $wireVersionForCollation = 5;
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var array|object */
private $filter;
/** @var integer */
private $limit;
/** @var array */
private $options;
/**
@ -72,7 +85,7 @@ class Delete implements Executable, Explainable
*/
public function __construct($databaseName, $collectionName, $filter, $limit, array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
if (! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}
@ -85,11 +98,11 @@ class Delete implements Executable, Explainable
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
@ -113,10 +126,15 @@ class Delete implements Executable, Explainable
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
$bulk = new Bulk();
$bulk->delete($this->filter, $this->createDeleteOptions());

View File

@ -18,8 +18,8 @@
namespace MongoDB\Operation;
use MongoDB\DeleteResult;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
@ -32,6 +32,7 @@ use MongoDB\Exception\UnsupportedException;
*/
class DeleteMany implements Executable, Explainable
{
/** @var Delete */
private $delete;
/**

View File

@ -18,8 +18,8 @@
namespace MongoDB\Operation;
use MongoDB\DeleteResult;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
@ -32,6 +32,7 @@ use MongoDB\Exception\UnsupportedException;
*/
class DeleteOne implements Executable, Explainable
{
/** @var Delete */
private $delete;
/**

View File

@ -18,14 +18,20 @@
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
use function current;
use function is_array;
use function is_integer;
use function is_object;
use function MongoDB\create_field_path_type_map;
use function MongoDB\server_supports_feature;
/**
* Operation for the distinct command.
@ -36,13 +42,25 @@ use MongoDB\Exception\UnsupportedException;
*/
class Distinct implements Executable, Explainable
{
/** @var integer */
private static $wireVersionForCollation = 5;
/** @var integer */
private static $wireVersionForReadConcern = 4;
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var string */
private $fieldName;
/** @var array|object */
private $filter;
/** @var array */
private $options;
/**
@ -69,6 +87,8 @@ class Distinct implements Executable, Explainable
*
* Sessions are not supported for server versions < 3.6.
*
* * typeMap (array): Type map for BSON deserialization.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param string $fieldName Field for which to return distinct values
@ -78,7 +98,7 @@ class Distinct implements Executable, Explainable
*/
public function __construct($databaseName, $collectionName, $fieldName, $filter = [], array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
if (! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}
@ -91,15 +111,19 @@ class Distinct implements Executable, Explainable
}
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
@ -125,18 +149,28 @@ class Distinct implements Executable, Explainable
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
if (isset($this->options['readConcern']) && ! server_supports_feature($server, self::$wireVersionForReadConcern)) {
throw UnsupportedException::readConcernNotSupported();
}
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['readConcern'])) {
throw UnsupportedException::readConcernNotSupportedInTransaction();
}
$cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
if (isset($this->options['typeMap'])) {
$cursor->setTypeMap(create_field_path_type_map($this->options['typeMap'], 'values.$'));
}
$result = current($cursor->toArray());
if ( ! isset($result->values) || ! is_array($result->values)) {
if (! isset($result->values) || ! is_array($result->values)) {
throw new UnexpectedValueException('distinct command did not return a "values" array');
}
@ -160,7 +194,7 @@ class Distinct implements Executable, Explainable
'key' => $this->fieldName,
];
if ( ! empty($this->filter)) {
if (! empty($this->filter)) {
$cmd['query'] = (object) $this->filter;
}

View File

@ -18,12 +18,15 @@
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use function current;
use function is_array;
use function MongoDB\server_supports_feature;
/**
* Operation for the drop command.
@ -35,11 +38,19 @@ use MongoDB\Exception\UnsupportedException;
*/
class DropCollection implements Executable
{
/** @var string */
private static $errorMessageNamespaceNotFound = 'ns not found';
/** @var integer */
private static $wireVersionForWriteConcern = 5;
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var array */
private $options;
/**
@ -67,7 +78,7 @@ class DropCollection implements Executable
public function __construct($databaseName, $collectionName, array $options = [])
{
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
@ -75,7 +86,7 @@ class DropCollection implements Executable
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
@ -98,10 +109,15 @@ class DropCollection implements Executable
*/
public function execute(Server $server)
{
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
$command = new Command(['drop' => $this->collectionName]);
try {

View File

@ -18,12 +18,15 @@
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use function current;
use function is_array;
use function MongoDB\server_supports_feature;
/**
* Operation for the dropDatabase command.
@ -35,9 +38,13 @@ use MongoDB\Exception\UnsupportedException;
*/
class DropDatabase implements Executable
{
/** @var integer */
private static $wireVersionForWriteConcern = 5;
/** @var string */
private $databaseName;
/** @var array */
private $options;
/**
@ -64,7 +71,7 @@ class DropDatabase implements Executable
public function __construct($databaseName, array $options = [])
{
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
@ -72,7 +79,7 @@ class DropDatabase implements Executable
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
@ -94,7 +101,7 @@ class DropDatabase implements Executable
*/
public function execute(Server $server)
{
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}

View File

@ -18,12 +18,16 @@
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use function current;
use function is_array;
use function is_integer;
use function MongoDB\server_supports_feature;
/**
* Operation for the dropIndexes command.
@ -34,11 +38,19 @@ use MongoDB\Exception\UnsupportedException;
*/
class DropIndexes implements Executable
{
/** @var integer */
private static $wireVersionForWriteConcern = 5;
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var string */
private $indexName;
/** @var array */
private $options;
/**
@ -80,7 +92,7 @@ class DropIndexes implements Executable
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
@ -88,7 +100,7 @@ class DropIndexes implements Executable
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
@ -112,10 +124,15 @@ class DropIndexes implements Executable
*/
public function execute(Server $server)
{
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
$cursor = $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions());
if (isset($this->options['typeMap'])) {

View File

@ -17,15 +17,12 @@
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
use function array_intersect_key;
/**
* Operation for obtaining an estimated count of documents in a collection
@ -36,13 +33,18 @@ use MongoDB\Exception\UnsupportedException;
*/
class EstimatedDocumentCount implements Executable, Explainable
{
private static $wireVersionForCollation = 5;
private static $wireVersionForReadConcern = 4;
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var array */
private $options;
/** @var Count */
private $count;
/**
* Constructs a count command.
*
@ -62,36 +64,18 @@ class EstimatedDocumentCount implements Executable, Explainable
*
* Sessions are not supported for server versions < 3.6.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array $options Command options
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, array $options = [])
{
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
unset($options['readConcern']);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->options = $options;
$this->options = array_intersect_key($options, ['maxTimeMS' => 1, 'readConcern' => 1, 'readPreference' => 1, 'session' => 1]);
$this->count = $this->createCount();
}
/**
@ -106,68 +90,19 @@ class EstimatedDocumentCount implements Executable, Explainable
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
throw UnsupportedException::readConcernNotSupported();
}
$cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
$result = current($cursor->toArray());
// Older server versions may return a float
if ( ! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) {
throw new UnexpectedValueException('count command did not return a numeric "n" value');
}
return (integer) $result->n;
return $this->count->execute($server);
}
public function getCommandDocument(Server $server)
{
return $this->createCommandDocument();
return $this->count->getCommandDocument($server);
}
/**
* Create the count command document.
*
* @return array
* @return Count
*/
private function createCommandDocument()
private function createCount()
{
$cmd = ['count' => $this->collectionName];
if (isset($this->options['maxTimeMS'])) {
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
return $cmd;
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executereadcommand.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['readConcern'])) {
$options['readConcern'] = $this->options['readConcern'];
}
if (isset($this->options['readPreference'])) {
$options['readPreference'] = $this->options['readPreference'];
}
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
return $options;
return new Count($this->databaseName, $this->collectionName, [], $this->options);
}
}

View File

@ -21,9 +21,12 @@ use MongoDB\Driver\Command;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Model\BSONDocument;
use MongoDB\Exception\UnsupportedException;
use function current;
use function is_array;
use function is_string;
use function MongoDB\server_supports_feature;
/**
* Operation for the explain command.
@ -38,11 +41,19 @@ class Explain implements Executable
const VERBOSITY_EXEC_STATS = 'executionStats';
const VERBOSITY_QUERY = 'queryPlanner';
/** @var integer */
private static $wireVersionForDistinct = 4;
/** @var integer */
private static $wireVersionForFindAndModify = 4;
/** @var string */
private $databaseName;
/** @var Explainable */
private $explainable;
/** @var array */
private $options;
/**
@ -59,19 +70,19 @@ class Explain implements Executable
*
* * verbosity (string): The mode in which the explain command will be run.
*
* @param string $databaseName Database name
* @param string $databaseName Database name
* @param Explainable $explainable Operation to explain
* @param array $options Command options
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, Explainable $explainable, array $options = [])
{
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
@ -89,11 +100,11 @@ class Explain implements Executable
public function execute(Server $server)
{
if ($this->explainable instanceof Distinct && ! \MongoDB\server_supports_feature($server, self::$wireVersionForDistinct)) {
if ($this->explainable instanceof Distinct && ! server_supports_feature($server, self::$wireVersionForDistinct)) {
throw UnsupportedException::explainNotSupported();
}
if ($this->isFindAndModify($this->explainable) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModify)) {
if ($this->isFindAndModify($this->explainable) && ! server_supports_feature($server, self::$wireVersionForFindAndModify)) {
throw UnsupportedException::explainNotSupported();
}
@ -138,6 +149,7 @@ class Explain implements Executable
if ($explainable instanceof FindAndModify || $explainable instanceof FindOneAndDelete || $explainable instanceof FindOneAndReplace || $explainable instanceof FindOneAndUpdate) {
return true;
}
return false;
}
}

View File

@ -27,5 +27,5 @@ use MongoDB\Driver\Server;
*/
interface Explainable extends Executable
{
function getCommandDocument(Server $server);
public function getCommandDocument(Server $server);
}

View File

@ -18,15 +18,23 @@
namespace MongoDB\Operation;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Query;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Model\BSONDocument;
use function is_array;
use function is_bool;
use function is_integer;
use function is_object;
use function is_string;
use function MongoDB\server_supports_feature;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* Operation for the find command.
*
@ -41,12 +49,22 @@ class Find implements Executable, Explainable
const TAILABLE = 2;
const TAILABLE_AWAIT = 3;
/** @var integer */
private static $wireVersionForCollation = 5;
/** @var integer */
private static $wireVersionForReadConcern = 4;
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var array|object */
private $filter;
/** @var array */
private $options;
/**
@ -147,7 +165,7 @@ class Find implements Executable, Explainable
*/
public function __construct($databaseName, $collectionName, $filter, array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
if (! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}
@ -168,7 +186,7 @@ class Find implements Executable, Explainable
}
if (isset($options['cursorType'])) {
if ( ! is_integer($options['cursorType'])) {
if (! is_integer($options['cursorType'])) {
throw InvalidArgumentException::invalidType('"cursorType" option', $options['cursorType'], 'integer');
}
@ -224,11 +242,11 @@ class Find implements Executable, Explainable
}
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
}
if (isset($options['returnKey']) && ! is_bool($options['returnKey'])) {
@ -236,7 +254,7 @@ class Find implements Executable, Explainable
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['showRecordId']) && ! is_bool($options['showRecordId'])) {
@ -288,14 +306,19 @@ class Find implements Executable, Explainable
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
if (isset($this->options['readConcern']) && ! server_supports_feature($server, self::$wireVersionForReadConcern)) {
throw UnsupportedException::readConcernNotSupported();
}
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['readConcern'])) {
throw UnsupportedException::readConcernNotSupportedInTransaction();
}
$cursor = $server->executeQuery($this->databaseName . '.' . $this->collectionName, new Query($this->filter, $this->createQueryOptions()), $this->createExecuteOptions());
if (isset($this->options['typeMap'])) {
@ -341,7 +364,7 @@ class Find implements Executable, Explainable
];
foreach ($modifierFallback as $modifier) {
if ( ! isset($options[$modifier[0]]) && isset($options['modifiers'][$modifier[1]])) {
if (! isset($options[$modifier[0]]) && isset($options['modifiers'][$modifier[1]])) {
$options[$modifier[0]] = $options['modifiers'][$modifier[1]];
}
}
@ -407,7 +430,7 @@ class Find implements Executable, Explainable
$modifiers = empty($this->options['modifiers']) ? [] : (array) $this->options['modifiers'];
if ( ! empty($modifiers)) {
if (! empty($modifiers)) {
$options['modifiers'] = $modifiers;
}

View File

@ -18,13 +18,21 @@
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
use function current;
use function is_array;
use function is_bool;
use function is_integer;
use function is_object;
use function MongoDB\create_field_path_type_map;
use function MongoDB\is_pipeline;
use function MongoDB\server_supports_feature;
/**
* Operation for the findAndModify command.
@ -37,13 +45,25 @@ use MongoDB\Exception\UnsupportedException;
*/
class FindAndModify implements Executable, Explainable
{
/** @var integer */
private static $wireVersionForArrayFilters = 6;
/** @var integer */
private static $wireVersionForCollation = 5;
/** @var integer */
private static $wireVersionForDocumentLevelValidation = 4;
/** @var integer */
private static $wireVersionForWriteConcern = 4;
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var array */
private $options;
/**
@ -137,7 +157,7 @@ class FindAndModify implements Executable, Explainable
throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if ( ! is_bool($options['new'])) {
if (! is_bool($options['new'])) {
throw InvalidArgumentException::invalidType('"new" option', $options['new'], 'boolean');
}
@ -145,12 +165,12 @@ class FindAndModify implements Executable, Explainable
throw InvalidArgumentException::invalidType('"query" option', $options['query'], 'array or object');
}
if ( ! is_bool($options['remove'])) {
if (! is_bool($options['remove'])) {
throw InvalidArgumentException::invalidType('"remove" option', $options['remove'], 'boolean');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) {
@ -166,14 +186,14 @@ class FindAndModify implements Executable, Explainable
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}
if ( ! is_bool($options['upsert'])) {
if (! is_bool($options['upsert'])) {
throw InvalidArgumentException::invalidType('"upsert" option', $options['upsert'], 'boolean');
}
if ( ! (isset($options['update']) xor $options['remove'])) {
if (! (isset($options['update']) xor $options['remove'])) {
throw new InvalidArgumentException('The "remove" option must be true or an "update" document must be specified, but not both');
}
@ -198,34 +218,32 @@ class FindAndModify implements Executable, Explainable
*/
public function execute(Server $server)
{
if (isset($this->options['arrayFilters']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForArrayFilters)) {
if (isset($this->options['arrayFilters']) && ! server_supports_feature($server, self::$wireVersionForArrayFilters)) {
throw UnsupportedException::arrayFiltersNotSupported();
}
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
$cursor = $server->executeWriteCommand($this->databaseName, new Command($this->createCommandDocument($server)), $this->createOptions());
$result = current($cursor->toArray());
if ( ! isset($result->value)) {
return null;
}
if ( ! is_object($result->value)) {
throw new UnexpectedValueException('findAndModify command did not return a "value" document');
}
if (isset($this->options['typeMap'])) {
return \MongoDB\apply_type_map_to_document($result->value, $this->options['typeMap']);
$cursor->setTypeMap(create_field_path_type_map($this->options['typeMap'], 'value'));
}
return $result->value;
$result = current($cursor->toArray());
return isset($result->value) ? $result->value : null;
}
public function getCommandDocument(Server $server)
@ -236,6 +254,7 @@ class FindAndModify implements Executable, Explainable
/**
* Create the findAndModify command document.
*
* @param Server $server
* @return array
*/
private function createCommandDocument(Server $server)
@ -249,12 +268,18 @@ class FindAndModify implements Executable, Explainable
$cmd['upsert'] = $this->options['upsert'];
}
foreach (['collation', 'fields', 'query', 'sort', 'update'] as $option) {
foreach (['collation', 'fields', 'query', 'sort'] as $option) {
if (isset($this->options[$option])) {
$cmd[$option] = (object) $this->options[$option];
}
}
if (isset($this->options['update'])) {
$cmd['update'] = is_pipeline($this->options['update'])
? $this->options['update']
: (object) $this->options['update'];
}
if (isset($this->options['arrayFilters'])) {
$cmd['arrayFilters'] = $this->options['arrayFilters'];
}
@ -263,7 +288,9 @@ class FindAndModify implements Executable, Explainable
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
if (! empty($this->options['bypassDocumentValidation']) &&
server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)
) {
$cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
}

View File

@ -17,10 +17,11 @@
namespace MongoDB\Operation;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use function current;
/**
* Operation for finding a single document with the find command.
@ -32,6 +33,7 @@ use MongoDB\Exception\UnsupportedException;
*/
class FindOne implements Executable, Explainable
{
/** @var Find */
private $find;
/**
@ -126,7 +128,7 @@ class FindOne implements Executable, Explainable
$cursor = $this->find->execute($server);
$document = current($cursor->toArray());
return ($document === false) ? null : $document;
return $document === false ? null : $document;
}
public function getCommandDocument(Server $server)

View File

@ -17,10 +17,12 @@
namespace MongoDB\Operation;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use function is_array;
use function is_object;
/**
* Operation for deleting a document with the findAndModify command.
@ -31,6 +33,7 @@ use MongoDB\Exception\UnsupportedException;
*/
class FindOneAndDelete implements Executable, Explainable
{
/** @var FindAndModify */
private $findAndModify;
/**
@ -71,7 +74,7 @@ class FindOneAndDelete implements Executable, Explainable
*/
public function __construct($databaseName, $collectionName, $filter, array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
if (! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}

View File

@ -17,10 +17,14 @@
namespace MongoDB\Operation;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use function is_array;
use function is_integer;
use function is_object;
use function MongoDB\is_first_key_operator;
/**
* Operation for replacing a document with the findAndModify command.
@ -34,6 +38,7 @@ class FindOneAndReplace implements Executable, Explainable
const RETURN_DOCUMENT_BEFORE = 1;
const RETURN_DOCUMENT_AFTER = 2;
/** @var FindAndModify */
private $findAndModify;
/**
@ -90,15 +95,15 @@ class FindOneAndReplace implements Executable, Explainable
*/
public function __construct($databaseName, $collectionName, $filter, $replacement, array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
if (! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}
if ( ! is_array($replacement) && ! is_object($replacement)) {
if (! is_array($replacement) && ! is_object($replacement)) {
throw InvalidArgumentException::invalidType('$replacement', $replacement, 'array or object');
}
if (\MongoDB\is_first_key_operator($replacement)) {
if (is_first_key_operator($replacement)) {
throw new InvalidArgumentException('First key in $replacement argument is an update operator');
}
@ -111,7 +116,7 @@ class FindOneAndReplace implements Executable, Explainable
throw InvalidArgumentException::invalidType('"projection" option', $options['projection'], 'array or object');
}
if ( ! is_integer($options['returnDocument'])) {
if (! is_integer($options['returnDocument'])) {
throw InvalidArgumentException::invalidType('"returnDocument" option', $options['returnDocument'], 'integer');
}

View File

@ -17,10 +17,15 @@
namespace MongoDB\Operation;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use function is_array;
use function is_integer;
use function is_object;
use function MongoDB\is_first_key_operator;
use function MongoDB\is_pipeline;
/**
* Operation for updating a document with the findAndModify command.
@ -34,6 +39,7 @@ class FindOneAndUpdate implements Executable, Explainable
const RETURN_DOCUMENT_BEFORE = 1;
const RETURN_DOCUMENT_AFTER = 2;
/** @var FindAndModify */
private $findAndModify;
/**
@ -93,16 +99,16 @@ class FindOneAndUpdate implements Executable, Explainable
*/
public function __construct($databaseName, $collectionName, $filter, $update, array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
if (! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}
if ( ! is_array($update) && ! is_object($update)) {
if (! is_array($update) && ! is_object($update)) {
throw InvalidArgumentException::invalidType('$update', $update, 'array or object');
}
if ( ! \MongoDB\is_first_key_operator($update)) {
throw new InvalidArgumentException('First key in $update argument is not an update operator');
if (! is_first_key_operator($update) && ! is_pipeline($update)) {
throw new InvalidArgumentException('Expected an update document with operator as first key or a pipeline');
}
$options += [
@ -114,7 +120,7 @@ class FindOneAndUpdate implements Executable, Explainable
throw InvalidArgumentException::invalidType('"projection" option', $options['projection'], 'array or object');
}
if ( ! is_integer($options['returnDocument'])) {
if (! is_integer($options['returnDocument'])) {
throw InvalidArgumentException::invalidType('"returnDocument" option', $options['returnDocument'], 'integer');
}

View File

@ -17,13 +17,19 @@
namespace MongoDB\Operation;
use MongoDB\InsertManyResult;
use MongoDB\Driver\BulkWrite as Bulk;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\InsertManyResult;
use function is_array;
use function is_bool;
use function is_object;
use function MongoDB\server_supports_feature;
use function sprintf;
/**
* Operation for inserting multiple documents with the insert command.
@ -34,11 +40,19 @@ use MongoDB\Exception\InvalidArgumentException;
*/
class InsertMany implements Executable
{
/** @var integer */
private static $wireVersionForDocumentLevelValidation = 4;
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var object[]|array[] */
private $documents;
/** @var array */
private $options;
/**
@ -81,7 +95,7 @@ class InsertMany implements Executable
throw new InvalidArgumentException(sprintf('$documents is not a list (unexpected index: "%s")', $i));
}
if ( ! is_array($document) && ! is_object($document)) {
if (! is_array($document) && ! is_object($document)) {
throw InvalidArgumentException::invalidType(sprintf('$documents[%d]', $i), $document, 'array or object');
}
@ -94,16 +108,16 @@ class InsertMany implements Executable
throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean');
}
if ( ! is_bool($options['ordered'])) {
if (! is_bool($options['ordered'])) {
throw InvalidArgumentException::invalidType('"ordered" option', $options['ordered'], 'boolean');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
@ -126,9 +140,16 @@ class InsertMany implements Executable
*/
public function execute(Server $server)
{
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
$options = ['ordered' => $this->options['ordered']];
if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
if (! empty($this->options['bypassDocumentValidation']) &&
server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)
) {
$options['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
}

View File

@ -17,13 +17,18 @@
namespace MongoDB\Operation;
use MongoDB\InsertOneResult;
use MongoDB\Driver\BulkWrite as Bulk;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\InsertOneResult;
use function is_array;
use function is_bool;
use function is_object;
use function MongoDB\server_supports_feature;
/**
* Operation for inserting a single document with the insert command.
@ -34,11 +39,19 @@ use MongoDB\Exception\InvalidArgumentException;
*/
class InsertOne implements Executable
{
/** @var integer */
private static $wireVersionForDocumentLevelValidation = 4;
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var array|object */
private $document;
/** @var array */
private $options;
/**
@ -66,7 +79,7 @@ class InsertOne implements Executable
*/
public function __construct($databaseName, $collectionName, $document, array $options = [])
{
if ( ! is_array($document) && ! is_object($document)) {
if (! is_array($document) && ! is_object($document)) {
throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
}
@ -75,11 +88,11 @@ class InsertOne implements Executable
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
@ -104,7 +117,14 @@ class InsertOne implements Executable
{
$options = [];
if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if (isset($this->options['writeConcern']) && $inTransaction) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
if (! empty($this->options['bypassDocumentValidation']) &&
server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)
) {
$options['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
}

View File

@ -18,15 +18,16 @@
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Query;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Model\CachingIterator;
use MongoDB\Model\CollectionInfoCommandIterator;
use MongoDB\Model\CollectionInfoIterator;
use MongoDB\Model\CollectionInfoLegacyIterator;
use function is_array;
use function is_integer;
use function is_object;
/**
* Operation for the listCollections command.
@ -37,7 +38,10 @@ use MongoDB\Model\CollectionInfoLegacyIterator;
*/
class ListCollections implements Executable
{
/** @var string */
private $databaseName;
/** @var array */
private $options;
/**
@ -69,7 +73,7 @@ class ListCollections implements Executable
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
$this->databaseName = (string) $databaseName;
@ -121,7 +125,7 @@ class ListCollections implements Executable
{
$cmd = ['listCollections' => 1];
if ( ! empty($this->options['filter'])) {
if (! empty($this->options['filter'])) {
$cmd['filter'] = (object) $this->options['filter'];
}
@ -129,7 +133,7 @@ class ListCollections implements Executable
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
$cursor = $server->executeCommand($this->databaseName, new Command($cmd), $this->createOptions());
$cursor = $server->executeReadCommand($this->databaseName, new Command($cmd), $this->createOptions());
$cursor->setTypeMap(['root' => 'array', 'document' => 'array']);
return new CollectionInfoCommandIterator(new CachingIterator($cursor));

View File

@ -18,13 +18,17 @@
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Model\DatabaseInfoIterator;
use MongoDB\Model\DatabaseInfoLegacyIterator;
use function current;
use function is_array;
use function is_integer;
use function is_object;
/**
* Operation for the ListDatabases command.
@ -35,6 +39,7 @@ use MongoDB\Model\DatabaseInfoLegacyIterator;
*/
class ListDatabases implements Executable
{
/** @var array */
private $options;
/**
@ -67,7 +72,7 @@ class ListDatabases implements Executable
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
$this->options = $options;
@ -86,7 +91,7 @@ class ListDatabases implements Executable
{
$cmd = ['listDatabases' => 1];
if ( ! empty($this->options['filter'])) {
if (! empty($this->options['filter'])) {
$cmd['filter'] = (object) $this->options['filter'];
}
@ -94,11 +99,11 @@ class ListDatabases implements Executable
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
$cursor = $server->executeCommand('admin', new Command($cmd), $this->createOptions());
$cursor = $server->executeReadCommand('admin', new Command($cmd), $this->createOptions());
$cursor->setTypeMap(['root' => 'array', 'document' => 'array']);
$result = current($cursor->toArray());
if ( ! isset($result['databases']) || ! is_array($result['databases'])) {
if (! isset($result['databases']) || ! is_array($result['databases'])) {
throw new UnexpectedValueException('listDatabases command did not return a "databases" array');
}

View File

@ -17,16 +17,16 @@
namespace MongoDB\Operation;
use EmptyIterator;
use MongoDB\Driver\Command;
use MongoDB\Driver\Query;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Model\CachingIterator;
use MongoDB\Model\IndexInfoIterator;
use MongoDB\Model\IndexInfoIteratorIterator;
use EmptyIterator;
use function is_integer;
/**
* Operation for the listIndexes command.
@ -37,11 +37,19 @@ use EmptyIterator;
*/
class ListIndexes implements Executable
{
/** @var integer */
private static $errorCodeDatabaseNotFound = 60;
/** @var integer */
private static $errorCodeNamespaceNotFound = 26;
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var array */
private $options;
/**
@ -68,7 +76,7 @@ class ListIndexes implements Executable
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
$this->databaseName = (string) $databaseName;
@ -126,14 +134,14 @@ class ListIndexes implements Executable
}
try {
$cursor = $server->executeCommand($this->databaseName, new Command($cmd), $this->createOptions());
$cursor = $server->executeReadCommand($this->databaseName, new Command($cmd), $this->createOptions());
} catch (DriverRuntimeException $e) {
/* The server may return an error if the collection does not exist.
* Check for possible error codes (see: SERVER-20463) and return an
* empty iterator instead of throwing.
*/
if ($e->getCode() === self::$errorCodeNamespaceNotFound || $e->getCode() === self::$errorCodeDatabaseNotFound) {
return new IndexInfoIteratorIterator(new EmptyIterator);
return new IndexInfoIteratorIterator(new EmptyIterator());
}
throw $e;

View File

@ -17,21 +17,29 @@
namespace MongoDB\Operation;
use ArrayIterator;
use MongoDB\BSON\JavascriptInterface;
use MongoDB\Driver\Command;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Model\TypeMapArrayIterator;
use MongoDB\MapReduceResult;
use ArrayIterator;
use stdClass;
use function current;
use function is_array;
use function is_bool;
use function is_integer;
use function is_object;
use function is_string;
use function MongoDB\create_field_path_type_map;
use function MongoDB\is_mapreduce_output_inline;
use function MongoDB\server_supports_feature;
/**
* Operation for the mapReduce command.
@ -42,16 +50,34 @@ use stdClass;
*/
class MapReduce implements Executable
{
/** @var integer */
private static $wireVersionForCollation = 5;
/** @var integer */
private static $wireVersionForDocumentLevelValidation = 4;
/** @var integer */
private static $wireVersionForReadConcern = 4;
/** @var integer */
private static $wireVersionForWriteConcern = 4;
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var JavascriptInterface */
private $map;
/** @var JavascriptInterface */
private $reduce;
/** @var array|object|string */
private $out;
/** @var array */
private $options;
/**
@ -144,7 +170,7 @@ class MapReduce implements Executable
*/
public function __construct($databaseName, $collectionName, JavascriptInterface $map, JavascriptInterface $reduce, $out, array $options = [])
{
if ( ! is_string($out) && ! is_array($out) && ! is_object($out)) {
if (! is_string($out) && ! is_array($out) && ! is_object($out)) {
throw InvalidArgumentException::invalidType('$out', $out, 'string or array or object');
}
@ -157,7 +183,7 @@ class MapReduce implements Executable
}
if (isset($options['finalize']) && ! $options['finalize'] instanceof JavascriptInterface) {
throw InvalidArgumentException::invalidType('"finalize" option', $options['finalize'], 'MongoDB\Driver\Javascript');
throw InvalidArgumentException::invalidType('"finalize" option', $options['finalize'], JavascriptInterface::class);
}
if (isset($options['jsMode']) && ! is_bool($options['jsMode'])) {
@ -177,11 +203,11 @@ class MapReduce implements Executable
}
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
}
if (isset($options['scope']) && ! is_array($options['scope']) && ! is_object($options['scope'])) {
@ -189,7 +215,7 @@ class MapReduce implements Executable
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) {
@ -205,7 +231,7 @@ class MapReduce implements Executable
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}
if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
@ -236,26 +262,46 @@ class MapReduce implements Executable
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
if (isset($this->options['readConcern']) && ! server_supports_feature($server, self::$wireVersionForReadConcern)) {
throw UnsupportedException::readConcernNotSupported();
}
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}
$hasOutputCollection = ! \MongoDB\is_mapreduce_output_inline($this->out);
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction) {
if (isset($this->options['readConcern'])) {
throw UnsupportedException::readConcernNotSupportedInTransaction();
}
if (isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
}
$hasOutputCollection = ! is_mapreduce_output_inline($this->out);
$command = $this->createCommand($server);
$options = $this->createOptions($hasOutputCollection);
/* If the mapReduce operation results in a write, use
* executeReadWriteCommand to ensure we're handling the writeConcern
* option.
* In other cases, we use executeCommand as this will prevent the
* mapReduce operation from being retried when retryReads is enabled.
* See https://github.com/mongodb/specifications/blob/master/source/retryable-reads/retryable-reads.rst#unsupported-read-operations. */
$cursor = $hasOutputCollection
? $server->executeReadWriteCommand($this->databaseName, $command, $options)
: $server->executeReadCommand($this->databaseName, $command, $options);
: $server->executeCommand($this->databaseName, $command, $options);
if (isset($this->options['typeMap']) && ! $hasOutputCollection) {
$cursor->setTypeMap(create_field_path_type_map($this->options['typeMap'], 'results.$'));
}
$result = current($cursor->toArray());
@ -291,7 +337,9 @@ class MapReduce implements Executable
}
}
if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
if (! empty($this->options['bypassDocumentValidation']) &&
server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)
) {
$cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
}
@ -312,11 +360,7 @@ class MapReduce implements Executable
if (isset($result->results) && is_array($result->results)) {
$results = $result->results;
return function() use ($results) {
if (isset($this->options['typeMap'])) {
return new TypeMapArrayIterator($results, $this->options['typeMap']);
}
return function () use ($results) {
return new ArrayIterator($results);
};
}
@ -328,7 +372,7 @@ class MapReduce implements Executable
? new Find($this->databaseName, $result->result, [], $options)
: new Find($result->result->db, $result->result->collection, [], $options);
return function() use ($find, $server) {
return function () use ($find, $server) {
return $find->execute($server);
};
}
@ -352,7 +396,7 @@ class MapReduce implements Executable
$options['readConcern'] = $this->options['readConcern'];
}
if ( ! $hasOutputCollection && isset($this->options['readPreference'])) {
if (! $hasOutputCollection && isset($this->options['readPreference'])) {
$options['readPreference'] = $this->options['readPreference'];
}

View File

@ -18,11 +18,15 @@
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use function current;
use function is_array;
use function MongoDB\server_supports_feature;
/**
* Operation for the collMod command.
@ -33,9 +37,16 @@ use MongoDB\Exception\InvalidArgumentException;
*/
class ModifyCollection implements Executable
{
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var array */
private $collectionOptions;
/** @var array */
private $options;
/**
@ -55,10 +66,10 @@ class ModifyCollection implements Executable
* This is not supported for server versions < 3.2 and will result in an
* exception at execution time if used.
*
* @param string $databaseName Database name
* @param string $collectionName Collection or view to modify
* @param string $collectionOptions Collection or view options to assign
* @param array $options Command options
* @param string $databaseName Database name
* @param string $collectionName Collection or view to modify
* @param array $collectionOptions Collection or view options to assign
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, array $collectionOptions, array $options = [])
@ -68,7 +79,7 @@ class ModifyCollection implements Executable
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
@ -76,7 +87,7 @@ class ModifyCollection implements Executable
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
@ -99,7 +110,7 @@ class ModifyCollection implements Executable
*/
public function execute(Server $server)
{
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}

View File

@ -17,11 +17,15 @@
namespace MongoDB\Operation;
use MongoDB\UpdateResult;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\UpdateResult;
use function is_array;
use function is_object;
use function MongoDB\is_first_key_operator;
use function MongoDB\is_pipeline;
/**
* Operation for replacing a single document with the update command.
@ -32,6 +36,7 @@ use MongoDB\Exception\UnsupportedException;
*/
class ReplaceOne implements Executable
{
/** @var Update */
private $update;
/**
@ -68,14 +73,18 @@ class ReplaceOne implements Executable
*/
public function __construct($databaseName, $collectionName, $filter, $replacement, array $options = [])
{
if ( ! is_array($replacement) && ! is_object($replacement)) {
if (! is_array($replacement) && ! is_object($replacement)) {
throw InvalidArgumentException::invalidType('$replacement', $replacement, 'array or object');
}
if (\MongoDB\is_first_key_operator($replacement)) {
if (is_first_key_operator($replacement)) {
throw new InvalidArgumentException('First key in $replacement argument is an update operator');
}
if (is_pipeline($replacement)) {
throw new InvalidArgumentException('$replacement argument is a pipeline');
}
$this->update = new Update(
$databaseName,
$collectionName,

View File

@ -17,14 +17,20 @@
namespace MongoDB\Operation;
use MongoDB\UpdateResult;
use MongoDB\Driver\BulkWrite as Bulk;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\UpdateResult;
use function is_array;
use function is_bool;
use function is_object;
use function MongoDB\is_first_key_operator;
use function MongoDB\is_pipeline;
use function MongoDB\server_supports_feature;
/**
* Operation for the update command.
@ -37,14 +43,28 @@ use MongoDB\Exception\UnsupportedException;
*/
class Update implements Executable, Explainable
{
/** @var integer */
private static $wireVersionForArrayFilters = 6;
/** @var integer */
private static $wireVersionForCollation = 5;
/** @var integer */
private static $wireVersionForDocumentLevelValidation = 4;
/** @var string */
private $databaseName;
/** @var string */
private $collectionName;
/** @var array|object */
private $filter;
/** @var array|object */
private $update;
/** @var array */
private $options;
/**
@ -92,11 +112,11 @@ class Update implements Executable, Explainable
*/
public function __construct($databaseName, $collectionName, $filter, $update, array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
if (! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}
if ( ! is_array($update) && ! is_object($update)) {
if (! is_array($update) && ! is_object($update)) {
throw InvalidArgumentException::invalidType('$update', $filter, 'array or object');
}
@ -117,24 +137,24 @@ class Update implements Executable, Explainable
throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
}
if ( ! is_bool($options['multi'])) {
if (! is_bool($options['multi'])) {
throw InvalidArgumentException::invalidType('"multi" option', $options['multi'], 'boolean');
}
if ($options['multi'] && ! \MongoDB\is_first_key_operator($update)) {
if ($options['multi'] && ! is_first_key_operator($update) && ! is_pipeline($update)) {
throw new InvalidArgumentException('"multi" option cannot be true if $update is a replacement document');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
}
if ( ! is_bool($options['upsert'])) {
if (! is_bool($options['upsert'])) {
throw InvalidArgumentException::invalidType('"upsert" option', $options['upsert'], 'boolean');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
@ -159,17 +179,24 @@ class Update implements Executable, Explainable
*/
public function execute(Server $server)
{
if (isset($this->options['arrayFilters']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForArrayFilters)) {
if (isset($this->options['arrayFilters']) && ! server_supports_feature($server, self::$wireVersionForArrayFilters)) {
throw UnsupportedException::arrayFiltersNotSupported();
}
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
$bulkOptions = [];
if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
if (! empty($this->options['bypassDocumentValidation']) &&
server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)
) {
$bulkOptions['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
}
@ -189,7 +216,9 @@ class Update implements Executable, Explainable
$cmd['writeConcern'] = $this->options['writeConcern'];
}
if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
if (! empty($this->options['bypassDocumentValidation']) &&
server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)
) {
$cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
}

View File

@ -17,11 +17,15 @@
namespace MongoDB\Operation;
use MongoDB\UpdateResult;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\UpdateResult;
use function is_array;
use function is_object;
use function MongoDB\is_first_key_operator;
use function MongoDB\is_pipeline;
/**
* Operation for updating multiple documents with the update command.
@ -32,6 +36,7 @@ use MongoDB\Exception\UnsupportedException;
*/
class UpdateMany implements Executable, Explainable
{
/** @var Update */
private $update;
/**
@ -74,12 +79,12 @@ class UpdateMany implements Executable, Explainable
*/
public function __construct($databaseName, $collectionName, $filter, $update, array $options = [])
{
if ( ! is_array($update) && ! is_object($update)) {
if (! is_array($update) && ! is_object($update)) {
throw InvalidArgumentException::invalidType('$update', $update, 'array or object');
}
if ( ! \MongoDB\is_first_key_operator($update)) {
throw new InvalidArgumentException('First key in $update argument is not an update operator');
if (! is_first_key_operator($update) && ! is_pipeline($update)) {
throw new InvalidArgumentException('Expected an update document with operator as first key or a pipeline');
}
$this->update = new Update(

View File

@ -17,11 +17,15 @@
namespace MongoDB\Operation;
use MongoDB\UpdateResult;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\UpdateResult;
use function is_array;
use function is_object;
use function MongoDB\is_first_key_operator;
use function MongoDB\is_pipeline;
/**
* Operation for updating a single document with the update command.
@ -32,6 +36,7 @@ use MongoDB\Exception\UnsupportedException;
*/
class UpdateOne implements Executable, Explainable
{
/** @var Update */
private $update;
/**
@ -74,12 +79,12 @@ class UpdateOne implements Executable, Explainable
*/
public function __construct($databaseName, $collectionName, $filter, $update, array $options = [])
{
if ( ! is_array($update) && ! is_object($update)) {
if (! is_array($update) && ! is_object($update)) {
throw InvalidArgumentException::invalidType('$update', $update, 'array or object');
}
if ( ! \MongoDB\is_first_key_operator($update)) {
throw new InvalidArgumentException('First key in $update argument is not an update operator');
if (! is_first_key_operator($update) && ! is_pipeline($update)) {
throw new InvalidArgumentException('Expected an update document with operator as first key or a pipeline');
}
$this->update = new Update(

View File

@ -17,23 +17,31 @@
namespace MongoDB\Operation;
use MongoDB\ChangeStream;
use MongoDB\BSON\TimestampInterface;
use MongoDB\Driver\Command;
use MongoDB\ChangeStream;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Exception\RuntimeException;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\Monitoring\CommandFailedEvent;
use MongoDB\Driver\Monitoring\CommandStartedEvent;
use MongoDB\Driver\Monitoring\CommandSubscriber;
use MongoDB\Driver\Monitoring\CommandSucceededEvent;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\Exception\RuntimeException;
use MongoDB\Driver\Monitoring\CommandFailedEvent;
use MongoDB\Driver\Monitoring\CommandSubscriber;
use MongoDB\Driver\Monitoring\CommandStartedEvent;
use MongoDB\Driver\Monitoring\CommandSucceededEvent;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Model\ChangeStreamIterator;
use function array_intersect_key;
use function array_unshift;
use function count;
use function is_array;
use function is_object;
use function is_string;
use function MongoDB\Driver\Monitoring\addSubscriber;
use function MongoDB\Driver\Monitoring\removeSubscriber;
use function MongoDB\select_server;
use function MongoDB\server_supports_feature;
/**
* Operation for creating a change stream with the aggregate command.
@ -47,19 +55,44 @@ use MongoDB\Exception\UnsupportedException;
*/
class Watch implements Executable, /* @internal */ CommandSubscriber
{
private static $wireVersionForOperationTime = 7;
const FULL_DOCUMENT_DEFAULT = 'default';
const FULL_DOCUMENT_UPDATE_LOOKUP = 'updateLookup';
/** @var integer */
private static $wireVersionForStartAtOperationTime = 7;
/** @var Aggregate */
private $aggregate;
/** @var array */
private $aggregateOptions;
/** @var array */
private $changeStreamOptions;
/** @var string|null */
private $collectionName;
/** @var string */
private $databaseName;
/** @var integer|null */
private $firstBatchSize;
/** @var boolean */
private $hasResumed = false;
/** @var Manager */
private $manager;
/** @var TimestampInterface */
private $operationTime;
/** @var array */
private $pipeline;
private $resumeCallable;
/** @var object|null */
private $postBatchResumeToken;
/**
* Constructs an aggregate command for creating a change stream.
@ -92,21 +125,31 @@ class Watch implements Executable, /* @internal */ CommandSubscriber
* * resumeAfter (document): Specifies the logical starting point for the
* new change stream.
*
* Using this option in conjunction with "startAtOperationTime" will
* result in a server error. The options are mutually exclusive.
* Using this option in conjunction with "startAfter" and/or
* "startAtOperationTime" will result in a server error. The options are
* mutually exclusive.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * startAfter (document): Specifies the logical starting point for the
* new change stream. Unlike "resumeAfter", this option can be used with
* a resume token from an "invalidate" event.
*
* Using this option in conjunction with "resumeAfter" and/or
* "startAtOperationTime" will result in a server error. The options are
* mutually exclusive.
*
* * startAtOperationTime (MongoDB\BSON\TimestampInterface): If specified,
* the change stream will only provide changes that occurred at or after
* the specified timestamp. Any command run against the server will
* return an operation time that can be used here. Alternatively, an
* operation time may be obtained from MongoDB\Driver\Server::getInfo().
*
* Using this option in conjunction with "resumeAfter" will result in a
* server error. The options are mutually exclusive.
* Using this option in conjunction with "resumeAfter" and/or
* "startAfter" will result in a server error. The options are mutually
* exclusive.
*
* This option is not supported for server versions < 4.0.
*
@ -117,11 +160,11 @@ class Watch implements Executable, /* @internal */ CommandSubscriber
* for the collection name. A cluster-level change stream may be created by
* specifying null for both the database and collection name.
*
* @param Manager $manager Manager instance from the driver
* @param string|null $databaseName Database name
* @param string|null $collectionName Collection name
* @param array $pipeline List of pipeline operations
* @param array $options Command options
* @param Manager $manager Manager instance from the driver
* @param string|null $databaseName Database name
* @param string|null $collectionName Collection name
* @param array $pipeline List of pipeline operations
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct(Manager $manager, $databaseName, $collectionName, array $pipeline, array $options = [])
@ -143,6 +186,10 @@ class Watch implements Executable, /* @internal */ CommandSubscriber
throw InvalidArgumentException::invalidType('"resumeAfter" option', $options['resumeAfter'], 'array or object');
}
if (isset($options['startAfter']) && ! is_array($options['startAfter']) && ! is_object($options['startAfter'])) {
throw InvalidArgumentException::invalidType('"startAfter" option', $options['startAfter'], 'array or object');
}
if (isset($options['startAtOperationTime']) && ! $options['startAtOperationTime'] instanceof TimestampInterface) {
throw InvalidArgumentException::invalidType('"startAtOperationTime" option', $options['startAtOperationTime'], TimestampInterface::class);
}
@ -152,7 +199,7 @@ class Watch implements Executable, /* @internal */ CommandSubscriber
* ("implicit from the user's perspective" per PHPLIB-342). Since this
* is filling in for an implicit session, we default "causalConsistency"
* to false. */
if ( ! isset($options['session'])) {
if (! isset($options['session'])) {
try {
$options['session'] = $manager->startSession(['causalConsistency' => false]);
} catch (RuntimeException $e) {
@ -162,7 +209,7 @@ class Watch implements Executable, /* @internal */ CommandSubscriber
}
$this->aggregateOptions = array_intersect_key($options, ['batchSize' => 1, 'collation' => 1, 'maxAwaitTimeMS' => 1, 'readConcern' => 1, 'readPreference' => 1, 'session' => 1, 'typeMap' => 1]);
$this->changeStreamOptions = array_intersect_key($options, ['fullDocument' => 1, 'resumeAfter' => 1, 'startAtOperationTime' => 1]);
$this->changeStreamOptions = array_intersect_key($options, ['fullDocument' => 1, 'resumeAfter' => 1, 'startAfter' => 1, 'startAtOperationTime' => 1]);
// Null database name implies a cluster-wide change stream
if ($databaseName === null) {
@ -170,12 +217,12 @@ class Watch implements Executable, /* @internal */ CommandSubscriber
$this->changeStreamOptions['allChangesForCluster'] = true;
}
$this->manager = $manager;
$this->databaseName = (string) $databaseName;
$this->collectionName = isset($collectionName) ? (string) $collectionName : null;
$this->pipeline = $pipeline;
$this->aggregate = $this->createAggregate();
$this->resumeCallable = $this->createResumeCallable($manager);
}
/** @internal */
@ -186,6 +233,12 @@ class Watch implements Executable, /* @internal */ CommandSubscriber
/** @internal */
final public function commandStarted(CommandStartedEvent $event)
{
if ($event->getCommandName() !== 'aggregate') {
return;
}
$this->firstBatchSize = null;
$this->postBatchResumeToken = null;
}
/** @internal */
@ -197,7 +250,18 @@ class Watch implements Executable, /* @internal */ CommandSubscriber
$reply = $event->getReply();
if (isset($reply->operationTime) && $reply->operationTime instanceof TimestampInterface) {
if (! isset($reply->cursor->firstBatch) || ! is_array($reply->cursor->firstBatch)) {
throw new UnexpectedValueException('aggregate command did not return a "cursor.firstBatch" array');
}
$this->firstBatchSize = count($reply->cursor->firstBatch);
if (isset($reply->cursor->postBatchResumeToken) && is_object($reply->cursor->postBatchResumeToken)) {
$this->postBatchResumeToken = $reply->cursor->postBatchResumeToken;
}
if ($this->shouldCaptureOperationTime($event->getServer()) &&
isset($reply->operationTime) && $reply->operationTime instanceof TimestampInterface) {
$this->operationTime = $reply->operationTime;
}
}
@ -213,11 +277,16 @@ class Watch implements Executable, /* @internal */ CommandSubscriber
*/
public function execute(Server $server)
{
return new ChangeStream($this->executeAggregate($server), $this->resumeCallable);
return new ChangeStream(
$this->createChangeStreamIterator($server),
function ($resumeToken, $hasAdvanced) {
return $this->resume($resumeToken, $hasAdvanced);
}
);
}
/**
* Create the aggregate command for creating a change stream.
* Create the aggregate command for a change stream.
*
* This method is also used to recreate the aggregate command when resuming.
*
@ -231,56 +300,139 @@ class Watch implements Executable, /* @internal */ CommandSubscriber
return new Aggregate($this->databaseName, $this->collectionName, $pipeline, $this->aggregateOptions);
}
private function createResumeCallable(Manager $manager)
/**
* Create a ChangeStreamIterator by executing the aggregate command.
*
* @param Server $server
* @return ChangeStreamIterator
*/
private function createChangeStreamIterator(Server $server)
{
return function($resumeToken = null) use ($manager) {
/* If a resume token was provided, update the "resumeAfter" option
* and ensure that "startAtOperationTime" is no longer set. */
if ($resumeToken !== null) {
$this->changeStreamOptions['resumeAfter'] = $resumeToken;
unset($this->changeStreamOptions['startAtOperationTime']);
}
/* If we captured an operation time from the first aggregate command
* and there is no "resumeAfter" option, set "startAtOperationTime"
* so that we can resume from the original aggregate's time. */
if ($this->operationTime !== null && ! isset($this->changeStreamOptions['resumeAfter'])) {
$this->changeStreamOptions['startAtOperationTime'] = $this->operationTime;
}
$this->aggregate = $this->createAggregate();
/* Select a new server using the read preference, execute this
* operation on it, and return the new ChangeStream. */
$server = $manager->selectServer($this->aggregateOptions['readPreference']);
return $this->execute($server);
};
return new ChangeStreamIterator(
$this->executeAggregate($server),
$this->firstBatchSize,
$this->getInitialResumeToken(),
$this->postBatchResumeToken
);
}
/**
* Execute the aggregate command and optionally capture its operation time.
* Execute the aggregate command.
*
* The command will be executed using APM so that we can capture data from
* its response (e.g. firstBatch size, postBatchResumeToken).
*
* @param Server $server
* @return Cursor
*/
private function executeAggregate(Server $server)
{
/* If we've already captured an operation time or the server does not
* support returning an operation time (e.g. MongoDB 3.6), execute the
* aggregation directly and return its cursor. */
if ($this->operationTime !== null || ! \MongoDB\server_supports_feature($server, self::$wireVersionForOperationTime)) {
return $this->aggregate->execute($server);
}
/* Otherwise, execute the aggregation using command monitoring so that
* we can capture its operation time with commandSucceeded(). */
\MongoDB\Driver\Monitoring\addSubscriber($this);
addSubscriber($this);
try {
return $this->aggregate->execute($server);
} finally {
\MongoDB\Driver\Monitoring\removeSubscriber($this);
removeSubscriber($this);
}
}
/**
* Return the initial resume token for creating the ChangeStreamIterator.
*
* @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#updating-the-cached-resume-token
* @return array|object|null
*/
private function getInitialResumeToken()
{
if ($this->firstBatchSize === 0 && isset($this->postBatchResumeToken)) {
return $this->postBatchResumeToken;
}
if (isset($this->changeStreamOptions['startAfter'])) {
return $this->changeStreamOptions['startAfter'];
}
if (isset($this->changeStreamOptions['resumeAfter'])) {
return $this->changeStreamOptions['resumeAfter'];
}
return null;
}
/**
* Resumes a change stream.
*
* @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#resume-process
* @param array|object|null $resumeToken
* @param bool $hasAdvanced
* @return ChangeStreamIterator
* @throws InvalidArgumentException
*/
private function resume($resumeToken = null, $hasAdvanced = false)
{
if (isset($resumeToken) && ! is_array($resumeToken) && ! is_object($resumeToken)) {
throw InvalidArgumentException::invalidType('$resumeToken', $resumeToken, 'array or object');
}
$this->hasResumed = true;
/* Select a new server using the original read preference. While watch
* is not usable within transactions, we still check if there is a
* pinned session. This is to avoid an ambiguous error message about
* running a command on the wrong server. */
$server = select_server($this->manager, $this->aggregateOptions);
$resumeOption = isset($this->changeStreamOptions['startAfter']) && ! $hasAdvanced ? 'startAfter' : 'resumeAfter';
unset($this->changeStreamOptions['resumeAfter']);
unset($this->changeStreamOptions['startAfter']);
unset($this->changeStreamOptions['startAtOperationTime']);
if ($resumeToken !== null) {
$this->changeStreamOptions[$resumeOption] = $resumeToken;
}
if ($resumeToken === null && $this->operationTime !== null) {
$this->changeStreamOptions['startAtOperationTime'] = $this->operationTime;
}
// Recreate the aggregate command and return a new ChangeStreamIterator
$this->aggregate = $this->createAggregate();
return $this->createChangeStreamIterator($server);
}
/**
* Determine whether to capture operation time from an aggregate response.
*
* @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#startatoperationtime
* @param Server $server
* @return boolean
*/
private function shouldCaptureOperationTime(Server $server)
{
if ($this->hasResumed) {
return false;
}
if (isset($this->changeStreamOptions['resumeAfter']) ||
isset($this->changeStreamOptions['startAfter']) ||
isset($this->changeStreamOptions['startAtOperationTime'])) {
return false;
}
if ($this->firstBatchSize > 0) {
return false;
}
if ($this->postBatchResumeToken !== null) {
return false;
}
if (! server_supports_feature($server, self::$wireVersionForStartAtOperationTime)) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,127 @@
<?php
namespace MongoDB\Operation;
use Exception;
use MongoDB\Driver\Exception\RuntimeException;
use MongoDB\Driver\Session;
use function call_user_func;
use function time;
/**
* @internal
*/
class WithTransaction
{
/** @var callable */
private $callback;
/** @var array */
private $transactionOptions;
/**
* @see Session::startTransaction for supported transaction options
*
* @param callable $callback A callback that will be invoked within the transaction
* @param array $transactionOptions Additional options that are passed to Session::startTransaction
*/
public function __construct(callable $callback, array $transactionOptions = [])
{
$this->callback = $callback;
$this->transactionOptions = $transactionOptions;
}
/**
* Execute the operation in the given session
*
* This helper takes care of retrying the commit operation or the entire
* transaction if an error occurs.
*
* If the commit fails because of an UnknownTransactionCommitResult error, the
* commit is retried without re-invoking the callback.
* If the commit fails because of a TransientTransactionError, the entire
* transaction will be retried. In this case, the callback will be invoked
* again. It is important that the logic inside the callback is idempotent.
*
* In case of failures, the commit or transaction are retried until 120 seconds
* from the initial call have elapsed. After that, no retries will happen and
* the helper will throw the last exception received from the driver.
*
* @see Client::startSession
*
* @param Session $session A session object as retrieved by Client::startSession
* @return void
* @throws RuntimeException for driver errors while committing the transaction
* @throws Exception for any other errors, including those thrown in the callback
*/
public function execute(Session $session)
{
$startTime = time();
while (true) {
$session->startTransaction($this->transactionOptions);
try {
call_user_func($this->callback, $session);
} catch (Exception $e) {
if ($session->isInTransaction()) {
$session->abortTransaction();
}
if ($e instanceof RuntimeException &&
$e->hasErrorLabel('TransientTransactionError') &&
! $this->isTransactionTimeLimitExceeded($startTime)
) {
continue;
}
throw $e;
}
if (! $session->isInTransaction()) {
// Assume callback intentionally ended the transaction
return;
}
while (true) {
try {
$session->commitTransaction();
} catch (RuntimeException $e) {
if ($e->getCode() !== 50 /* MaxTimeMSExpired */ &&
$e->hasErrorLabel('UnknownTransactionCommitResult') &&
! $this->isTransactionTimeLimitExceeded($startTime)
) {
// Retry committing the transaction
continue;
}
if ($e->hasErrorLabel('TransientTransactionError') &&
! $this->isTransactionTimeLimitExceeded($startTime)
) {
// Restart the transaction, invoking the callback again
continue 2;
}
throw $e;
}
// Commit was successful
break;
}
// Transaction was successful
break;
}
}
/**
* Returns whether the time limit for retrying transactions in the convenient transaction API has passed
*
* @param int $startTime The time the transaction was started
* @return bool
*/
private function isTransactionTimeLimitExceeded($startTime)
{
return time() - $startTime >= 120;
}
}

View File

@ -25,14 +25,12 @@ use MongoDB\Exception\BadMethodCallException;
*/
class UpdateResult
{
/** @var WriteResult */
private $writeResult;
/** @var boolean */
private $isAcknowledged;
/**
* Constructor.
*
* @param WriteResult $writeResult
*/
public function __construct(WriteResult $writeResult)
{
$this->writeResult = $writeResult;

View File

@ -17,13 +17,28 @@
namespace MongoDB;
use Exception;
use MongoDB\BSON\Serializable;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Session;
use MongoDB\Exception\InvalidArgumentException;
use stdClass;
use MongoDB\Exception\RuntimeException;
use MongoDB\Operation\WithTransaction;
use ReflectionClass;
use ReflectionException;
use function end;
use function get_object_vars;
use function in_array;
use function is_array;
use function is_object;
use function is_string;
use function key;
use function MongoDB\BSON\fromPHP;
use function MongoDB\BSON\toPHP;
use function reset;
use function substr;
/**
* Applies a type map to a document.
@ -40,11 +55,11 @@ use ReflectionClass;
*/
function apply_type_map_to_document($document, array $typeMap)
{
if ( ! is_array($document) && ! is_object($document)) {
if (! is_array($document) && ! is_object($document)) {
throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
}
return \MongoDB\BSON\toPHP(\MongoDB\BSON\fromPHP($document), $typeMap);
return toPHP(fromPHP($document), $typeMap);
}
/**
@ -66,7 +81,7 @@ function generate_index_name($document)
$document = get_object_vars($document);
}
if ( ! is_array($document)) {
if (! is_array($document)) {
throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
}
@ -99,18 +114,75 @@ function is_first_key_operator($document)
$document = get_object_vars($document);
}
if ( ! is_array($document)) {
if (! is_array($document)) {
throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
}
reset($document);
$firstKey = (string) key($document);
return (isset($firstKey[0]) && $firstKey[0] === '$');
return isset($firstKey[0]) && $firstKey[0] === '$';
}
/**
* Return whether the aggregation pipeline ends with an $out operator.
* Returns whether an update specification is a valid aggregation pipeline.
*
* @internal
* @param mixed $pipeline
* @return boolean
*/
function is_pipeline($pipeline)
{
if (! is_array($pipeline)) {
return false;
}
if ($pipeline === []) {
return false;
}
$expectedKey = 0;
foreach ($pipeline as $key => $stage) {
if (! is_array($stage) && ! is_object($stage)) {
return false;
}
if ($expectedKey !== $key) {
return false;
}
$expectedKey++;
$stage = (array) $stage;
reset($stage);
$key = key($stage);
if (! isset($key[0]) || $key[0] !== '$') {
return false;
}
}
return true;
}
/**
* Returns whether we are currently in a transaction.
*
* @internal
* @param array $options Command options
* @return boolean
*/
function is_in_transaction(array $options)
{
if (isset($options['session']) && $options['session'] instanceof Session && $options['session']->isInTransaction()) {
return true;
}
return false;
}
/**
* Return whether the aggregation pipeline ends with an $out or $merge operator.
*
* This is used for determining whether the aggregation pipeline must be
* executed against a primary server.
@ -119,7 +191,7 @@ function is_first_key_operator($document)
* @param array $pipeline List of pipeline operations
* @return boolean
*/
function is_last_pipeline_operator_out(array $pipeline)
function is_last_pipeline_operator_write(array $pipeline)
{
$lastOp = end($pipeline);
@ -129,7 +201,7 @@ function is_last_pipeline_operator_out(array $pipeline)
$lastOp = (array) $lastOp;
return key($lastOp) === '$out';
return in_array(key($lastOp), ['$out', '$merge'], true);
}
/**
@ -145,7 +217,7 @@ function is_last_pipeline_operator_out(array $pipeline)
*/
function is_mapreduce_output_inline($out)
{
if ( ! is_array($out) && ! is_object($out)) {
if (! is_array($out) && ! is_object($out)) {
return false;
}
@ -157,7 +229,7 @@ function is_mapreduce_output_inline($out)
$out = get_object_vars($out);
}
if ( ! is_array($out)) {
if (! is_array($out)) {
throw InvalidArgumentException::invalidType('$out', $out, 'array or object');
}
@ -180,18 +252,20 @@ function server_supports_feature(Server $server, $feature)
$maxWireVersion = isset($info['maxWireVersion']) ? (integer) $info['maxWireVersion'] : 0;
$minWireVersion = isset($info['minWireVersion']) ? (integer) $info['minWireVersion'] : 0;
return ($minWireVersion <= $feature && $maxWireVersion >= $feature);
return $minWireVersion <= $feature && $maxWireVersion >= $feature;
}
function is_string_array($input) {
if (!is_array($input)){
function is_string_array($input)
{
if (! is_array($input)) {
return false;
}
foreach($input as $item) {
if (!is_string($item)) {
foreach ($input as $item) {
if (! is_string($item)) {
return false;
}
}
return true;
}
@ -204,22 +278,155 @@ function is_string_array($input) {
* @see https://bugs.php.net/bug.php?id=49664
* @param mixed $element Value to be copied
* @return mixed
* @throws ReflectionException
*/
function recursive_copy($element) {
function recursive_copy($element)
{
if (is_array($element)) {
foreach ($element as $key => $value) {
$element[$key] = recursive_copy($value);
}
return $element;
}
if ( ! is_object($element)) {
if (! is_object($element)) {
return $element;
}
if ( ! (new ReflectionClass($element))->isCloneable()) {
if (! (new ReflectionClass($element))->isCloneable()) {
return $element;
}
return clone $element;
}
/**
* Creates a type map to apply to a field type
*
* This is used in the Aggregate, Distinct, and FindAndModify operations to
* apply the root-level type map to the document that will be returned. It also
* replaces the root type with object for consistency within these operations
*
* An existing type map for the given field path will not be overwritten
*
* @internal
* @param array $typeMap The existing typeMap
* @param string $fieldPath The field path to apply the root type to
* @return array
*/
function create_field_path_type_map(array $typeMap, $fieldPath)
{
// If some field paths already exist, we prefix them with the field path we are assuming as the new root
if (isset($typeMap['fieldPaths']) && is_array($typeMap['fieldPaths'])) {
$fieldPaths = $typeMap['fieldPaths'];
$typeMap['fieldPaths'] = [];
foreach ($fieldPaths as $existingFieldPath => $type) {
$typeMap['fieldPaths'][$fieldPath . '.' . $existingFieldPath] = $type;
}
}
// If a root typemap was set, apply this to the field object
if (isset($typeMap['root'])) {
$typeMap['fieldPaths'][$fieldPath] = $typeMap['root'];
}
/* Special case if we want to convert an array, in which case we need to
* ensure that the field containing the array is exposed as an array,
* instead of the type given in the type map's array key. */
if (substr($fieldPath, -2, 2) === '.$') {
$typeMap['fieldPaths'][substr($fieldPath, 0, -2)] = 'array';
}
$typeMap['root'] = 'object';
return $typeMap;
}
/**
* Execute a callback within a transaction in the given session
*
* This helper takes care of retrying the commit operation or the entire
* transaction if an error occurs.
*
* If the commit fails because of an UnknownTransactionCommitResult error, the
* commit is retried without re-invoking the callback.
* If the commit fails because of a TransientTransactionError, the entire
* transaction will be retried. In this case, the callback will be invoked
* again. It is important that the logic inside the callback is idempotent.
*
* In case of failures, the commit or transaction are retried until 120 seconds
* from the initial call have elapsed. After that, no retries will happen and
* the helper will throw the last exception received from the driver.
*
* @see Client::startSession
* @see Session::startTransaction for supported transaction options
*
* @param Session $session A session object as retrieved by Client::startSession
* @param callable $callback A callback that will be invoked within the transaction
* @param array $transactionOptions Additional options that are passed to Session::startTransaction
* @return void
* @throws RuntimeException for driver errors while committing the transaction
* @throws Exception for any other errors, including those thrown in the callback
*/
function with_transaction(Session $session, callable $callback, array $transactionOptions = [])
{
$operation = new WithTransaction($callback, $transactionOptions);
$operation->execute($session);
}
/**
* Returns the session option if it is set and valid.
*
* @internal
* @param array $options
* @return Session|null
*/
function extract_session_from_options(array $options)
{
if (! isset($options['session']) || ! $options['session'] instanceof Session) {
return null;
}
return $options['session'];
}
/**
* Returns the readPreference option if it is set and valid.
*
* @internal
* @param array $options
* @return ReadPreference|null
*/
function extract_read_preference_from_options(array $options)
{
if (! isset($options['readPreference']) || ! $options['readPreference'] instanceof ReadPreference) {
return null;
}
return $options['readPreference'];
}
/**
* Performs server selection, respecting the readPreference and session options
* (if given)
*
* @internal
* @return Server
*/
function select_server(Manager $manager, array $options)
{
$session = extract_session_from_options($options);
if ($session instanceof Session && $session->getServer() !== null) {
return $session->getServer();
}
$readPreference = extract_read_preference_from_options($options);
if (! $readPreference instanceof ReadPreference) {
// TODO: PHPLIB-476: Read transaction read preference once PHPC-1439 is implemented
$readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
}
return $manager->selectServer($readPreference);
}

View File

@ -1,6 +1,6 @@
MongoDB PHP
-----------
Download from https://github.com/mongodb/mongo-php-library
Download from https://github.com/mongodb/mongo-php-library/releases
Import procedure:
@ -8,8 +8,4 @@ Import procedure:
- Copy the license file from the project root.
- Update thirdpartylibs.xml with the latest version.
2019/03/14
----------
Last commit on download: aac8e54009196f6544e50baf9b63dcf0eab3bbdf
This version (1.4.2) requires PHP mongodb extension >= 1.5
This version (1.5.1) requires PHP mongodb extension >= 1.6.0

View File

@ -4,7 +4,7 @@
<location>MongoDB</location>
<name>MongoDB PHP Library</name>
<license>Apache</license>
<version>1.4.2</version>
<version>1.5.1</version>
<licenseversion>2.0</licenseversion>
</library>
</libraries>