/
home
/
obinna
/
html
/
cravings
/
vendor
/
mongodb
/
mongodb
/
tests
/
SpecTests
/
Upload File
HOME
<?php namespace MongoDB\Tests\SpecTests; use LogicException; use MongoDB\Client; use MongoDB\Collection; use MongoDB\Database; use MongoDB\Driver\Cursor; use MongoDB\Driver\Exception\BulkWriteException; use MongoDB\Driver\Exception\Exception; use MongoDB\Driver\Server; use MongoDB\Driver\Session; use MongoDB\Driver\WriteConcern; use MongoDB\GridFS\Bucket; use MongoDB\Operation\FindOneAndReplace; use MongoDB\Operation\FindOneAndUpdate; use stdClass; use function array_diff_key; use function array_map; use function fclose; use function fopen; use function MongoDB\is_last_pipeline_operator_write; use function MongoDB\with_transaction; use function stream_get_contents; use function strtolower; /** * Spec test operation. */ final class Operation { const OBJECT_CLIENT = 'client'; const OBJECT_COLLECTION = 'collection'; const OBJECT_DATABASE = 'database'; const OBJECT_GRIDFS_BUCKET = 'gridfsbucket'; const OBJECT_SELECT_COLLECTION = 'selectCollection'; const OBJECT_SELECT_DATABASE = 'selectDatabase'; const OBJECT_SESSION0 = 'session0'; const OBJECT_SESSION1 = 'session1'; const OBJECT_TEST_RUNNER = 'testRunner'; /** @var ErrorExpectation|null */ public $errorExpectation; /** @var ResultExpectation|null */ public $resultExpectation; /** @var array */ private $arguments = []; /** @var string|null */ private $collectionName; /** @var array */ private $collectionOptions = []; /** @var string|null */ private $databaseName; /** @var string */ private $name; /** @var string */ private $object = self::OBJECT_COLLECTION; private function __construct(stdClass $operation) { $this->name = $operation->name; if (isset($operation->arguments)) { $this->arguments = (array) $operation->arguments; } if (isset($operation->object)) { $this->object = $operation->object; } } public static function fromChangeStreams(stdClass $operation) { $o = new self($operation); /* Note: change streams only return majority-committed writes, so ensure * each operation applies that write concern. This will avoid spurious * test failures. */ $writeConcern = new WriteConcern(WriteConcern::MAJORITY); // Expect all operations to succeed $o->errorExpectation = ErrorExpectation::noError(); /* The Change Streams spec tests include a unique "rename" operation, * which we should convert to a renameCollection command to be run * against the admin database. */ if ($operation->name === 'rename') { $o->object = self::OBJECT_SELECT_DATABASE; $o->databaseName = 'admin'; $o->name = 'runCommand'; $o->arguments = [ 'command' => [ 'renameCollection' => $operation->database . '.' . $operation->collection, 'to' => $operation->database . '.' . $operation->arguments->to, // Note: Database::command() does not inherit WC, so be explicit 'writeConcern' => $writeConcern, ], ]; return $o; } $o->databaseName = $operation->database; $o->collectionName = $operation->collection; $o->collectionOptions = ['writeConcern' => $writeConcern]; $o->object = self::OBJECT_SELECT_COLLECTION; return $o; } public static function fromClientSideEncryption(stdClass $operation) { $o = new self($operation); $o->errorExpectation = ErrorExpectation::fromClientSideEncryption($operation); $o->resultExpectation = ResultExpectation::fromClientSideEncryption($operation, $o->getResultAssertionType()); if (isset($operation->collectionOptions)) { $o->collectionOptions = (array) $operation->collectionOptions; } return $o; } public static function fromCommandMonitoring(stdClass $operation) { $o = new self($operation); if (isset($operation->collectionOptions)) { $o->collectionOptions = (array) $operation->collectionOptions; } /* We purposefully avoid setting a default error expectation, because * some tests may trigger a write or command error. */ return $o; } /** * This method is exclusively used to prepare nested operations for the * withTransaction session operation * * @return Operation */ private static function fromConvenientTransactions(stdClass $operation) { $o = new self($operation); if (isset($operation->error)) { $o->errorExpectation = ErrorExpectation::fromTransactions($operation); } $o->resultExpectation = ResultExpectation::fromTransactions($operation, $o->getResultAssertionType()); if (isset($operation->collectionOptions)) { $o->collectionOptions = (array) $operation->collectionOptions; } return $o; } public static function fromCrud(stdClass $operation) { $o = new self($operation); $o->errorExpectation = ErrorExpectation::fromCrud($operation); $o->resultExpectation = ResultExpectation::fromCrud($operation, $o->getResultAssertionType()); if (isset($operation->collectionOptions)) { $o->collectionOptions = (array) $operation->collectionOptions; } return $o; } public static function fromRetryableReads(stdClass $operation) { $o = new self($operation); $o->errorExpectation = ErrorExpectation::fromRetryableReads($operation); $o->resultExpectation = ResultExpectation::fromRetryableReads($operation, $o->getResultAssertionType()); return $o; } public static function fromRetryableWrites(stdClass $operation, stdClass $outcome) { $o = new self($operation); $o->errorExpectation = ErrorExpectation::fromRetryableWrites($outcome); $o->resultExpectation = ResultExpectation::fromRetryableWrites($outcome, $o->getResultAssertionType()); return $o; } public static function fromTransactions(stdClass $operation) { $o = new self($operation); $o->errorExpectation = ErrorExpectation::fromTransactions($operation); $o->resultExpectation = ResultExpectation::fromTransactions($operation, $o->getResultAssertionType()); if (isset($operation->collectionOptions)) { $o->collectionOptions = (array) $operation->collectionOptions; } return $o; } /** * Execute the operation and assert its outcome. * * @param FunctionalTestCase $test Test instance * @param Context $context Execution context * @param bool $bubbleExceptions If true, any exception that was caught is rethrown */ public function assert(FunctionalTestCase $test, Context $context, $bubbleExceptions = false) { $result = null; $exception = null; try { $result = $this->execute($test, $context); /* Eagerly iterate the results of a cursor. This both allows an * exception to be thrown sooner and ensures that any expected * getMore command(s) can be observed even if a ResultExpectation * is not used (e.g. Command Monitoring spec). */ if ($result instanceof Cursor) { $result = $result->toArray(); } } catch (Exception $e) { $exception = $e; } // Extract incomplete result for failed bulkWrite and insertMany operations if ($exception instanceof BulkWriteException) { $result = $exception->getWriteResult(); } if (isset($this->errorExpectation)) { $this->errorExpectation->assert($test, $exception); } if (isset($this->resultExpectation)) { $this->resultExpectation->assert($test, $result); } if ($exception && $bubbleExceptions) { throw $exception; } } /** * Executes the operation with a given context. * * @param Context $context Execution context * @return mixed * @throws LogicException if the operation is unsupported */ private function execute(FunctionalTestCase $test, Context $context) { switch ($this->object) { case self::OBJECT_CLIENT: $client = $context->getClient(); return $this->executeForClient($client, $context); case self::OBJECT_COLLECTION: $collection = $context->getCollection($this->collectionOptions); return $this->executeForCollection($collection, $context); case self::OBJECT_DATABASE: $database = $context->getDatabase(); return $this->executeForDatabase($database, $context); case self::OBJECT_GRIDFS_BUCKET: $bucket = $context->getGridFSBucket(); return $this->executeForGridFSBucket($bucket, $context); case self::OBJECT_SELECT_COLLECTION: $collection = $context->selectCollection($this->databaseName, $this->collectionName, $this->collectionOptions); return $this->executeForCollection($collection, $context); case self::OBJECT_SELECT_DATABASE: $database = $context->selectDatabase($this->databaseName); return $this->executeForDatabase($database, $context); case self::OBJECT_SESSION0: return $this->executeForSession($context->session0, $test, $context); case self::OBJECT_SESSION1: return $this->executeForSession($context->session1, $test, $context); case self::OBJECT_TEST_RUNNER: return $this->executeForTestRunner($test, $context); default: throw new LogicException('Unsupported object: ' . $this->object); } } /** * Executes the client operation and return its result. * * @param Client $client * @param Context $context Execution context * @return mixed * @throws LogicException if the collection operation is unsupported */ private function executeForClient(Client $client, Context $context) { $args = $context->prepareOptions($this->arguments); $context->replaceArgumentSessionPlaceholder($args); switch ($this->name) { case 'listDatabases': return $client->listDatabases($args); case 'watch': return $client->watch( isset($args['pipeline']) ? $args['pipeline'] : [], array_diff_key($args, ['pipeline' => 1]) ); default: throw new LogicException('Unsupported client operation: ' . $this->name); } } /** * Executes the collection operation and return its result. * * @param Collection $collection * @param Context $context Execution context * @return mixed * @throws LogicException if the collection operation is unsupported */ private function executeForCollection(Collection $collection, Context $context) { $args = $context->prepareOptions($this->arguments); $context->replaceArgumentSessionPlaceholder($args); switch ($this->name) { case 'aggregate': return $collection->aggregate( $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) ); case 'bulkWrite': // Merge nested and top-level options (see: SPEC-1158) $options = isset($args['options']) ? (array) $args['options'] : []; $options += array_diff_key($args, ['requests' => 1]); return $collection->bulkWrite( // TODO: Check if self can be used with a private static function array_map([$this, 'prepareBulkWriteRequest'], $args['requests']), $options ); case 'count': case 'countDocuments': case 'find': return $collection->{$this->name}( isset($args['filter']) ? $args['filter'] : [], array_diff_key($args, ['filter' => 1]) ); case 'estimatedDocumentCount': return $collection->estimatedDocumentCount($args); case 'deleteMany': case 'deleteOne': case 'findOneAndDelete': return $collection->{$this->name}( $args['filter'], array_diff_key($args, ['filter' => 1]) ); case 'distinct': return $collection->distinct( $args['fieldName'], isset($args['filter']) ? $args['filter'] : [], array_diff_key($args, ['fieldName' => 1, 'filter' => 1]) ); case 'drop': return $collection->drop($args); case 'findOne': return $collection->findOne($args['filter'], array_diff_key($args, ['filter' => 1])); case 'findOneAndReplace': if (isset($args['returnDocument'])) { $args['returnDocument'] = 'after' === strtolower($args['returnDocument']) ? FindOneAndReplace::RETURN_DOCUMENT_AFTER : FindOneAndReplace::RETURN_DOCUMENT_BEFORE; } // Fall through case 'replaceOne': return $collection->{$this->name}( $args['filter'], $args['replacement'], array_diff_key($args, ['filter' => 1, 'replacement' => 1]) ); case 'findOneAndUpdate': if (isset($args['returnDocument'])) { $args['returnDocument'] = 'after' === strtolower($args['returnDocument']) ? FindOneAndUpdate::RETURN_DOCUMENT_AFTER : FindOneAndUpdate::RETURN_DOCUMENT_BEFORE; } // Fall through case 'updateMany': case 'updateOne': return $collection->{$this->name}( $args['filter'], $args['update'], array_diff_key($args, ['filter' => 1, 'update' => 1]) ); case 'insertMany': // Merge nested and top-level options (see: SPEC-1158) $options = isset($args['options']) ? (array) $args['options'] : []; $options += array_diff_key($args, ['documents' => 1]); return $collection->insertMany( $args['documents'], $options ); case 'insertOne': return $collection->insertOne( $args['document'], array_diff_key($args, ['document' => 1]) ); case 'listIndexes': return $collection->listIndexes($args); case 'mapReduce': return $collection->mapReduce( $args['map'], $args['reduce'], $args['out'], array_diff_key($args, ['map' => 1, 'reduce' => 1, 'out' => 1]) ); case 'watch': return $collection->watch( isset($args['pipeline']) ? $args['pipeline'] : [], array_diff_key($args, ['pipeline' => 1]) ); default: throw new LogicException('Unsupported collection operation: ' . $this->name); } } /** * Executes the database operation and return its result. * * @param Database $database * @param Context $context Execution context * @return mixed * @throws LogicException if the database operation is unsupported */ private function executeForDatabase(Database $database, Context $context) { $args = $context->prepareOptions($this->arguments); $context->replaceArgumentSessionPlaceholder($args); switch ($this->name) { case 'aggregate': return $database->aggregate( $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) ); case 'listCollections': return $database->listCollections($args); case 'runCommand': return $database->command( $args['command'], array_diff_key($args, ['command' => 1]) )->toArray()[0]; case 'watch': return $database->watch( isset($args['pipeline']) ? $args['pipeline'] : [], array_diff_key($args, ['pipeline' => 1]) ); default: throw new LogicException('Unsupported database operation: ' . $this->name); } } /** * Executes the GridFS bucket operation and return its result. * * @param Bucket $bucket * @param Context $context Execution context * @return mixed * @throws LogicException if the database operation is unsupported */ private function executeForGridFSBucket(Bucket $bucket, Context $context) { $args = $context->prepareOptions($this->arguments); $context->replaceArgumentSessionPlaceholder($args); switch ($this->name) { case 'download': $stream = fopen('php://memory', 'w+'); try { $bucket->downloadToStream($args['id'], $stream); return stream_get_contents($stream); } finally { fclose($stream); } break; case 'download_by_name': $stream = fopen('php://memory', 'w+'); try { $bucket->downloadToStreamByName($args['filename'], $stream, array_diff_key($args, ['filename' => 1])); return stream_get_contents($stream); } finally { fclose($stream); } break; default: throw new LogicException('Unsupported GridFS bucket operation: ' . $this->name); } } /** * Executes the session operation and return its result. * * @param Session $session * @param FunctionalTestCase $test * @param Context $context Execution context * @return mixed * @throws LogicException if the session operation is unsupported */ private function executeForSession(Session $session, FunctionalTestCase $test, Context $context) { switch ($this->name) { case 'abortTransaction': return $session->abortTransaction(); case 'commitTransaction': return $session->commitTransaction(); case 'startTransaction': $options = isset($this->arguments['options']) ? (array) $this->arguments['options'] : []; return $session->startTransaction($context->prepareOptions($options)); case 'withTransaction': /** @var self[] $callbackOperations */ $callbackOperations = array_map(function ($operation) { return self::fromConvenientTransactions($operation); }, $this->arguments['callback']->operations); $callback = function () use ($callbackOperations, $test, $context) { foreach ($callbackOperations as $operation) { $operation->assert($test, $context, true); } }; $options = isset($this->arguments['options']) ? (array) $this->arguments['options'] : []; return with_transaction($session, $callback, $context->prepareOptions($options)); default: throw new LogicException('Unsupported session operation: ' . $this->name); } } private function executeForTestRunner(FunctionalTestCase $test, Context $context) { $args = $context->prepareOptions($this->arguments); $context->replaceArgumentSessionPlaceholder($args); switch ($this->name) { case 'assertSessionPinned': $test->assertInstanceOf(Session::class, $args['session']); $test->assertInstanceOf(Server::class, $args['session']->getServer()); return null; case 'assertSessionTransactionState': $test->assertInstanceOf(Session::class, $args['session']); /* PHPC currently does not expose the exact session state, but * instead exposes a bool to let us know whether a transaction * is currently in progress. This code may fail down the line * and should be adjusted once PHPC-1438 is implemented. */ $test->assertSame($this->arguments['state'], $args['session']->getTransactionState()); return null; case 'assertSessionUnpinned': $test->assertInstanceOf(Session::class, $args['session']); $test->assertNull($args['session']->getServer()); return null; case 'targetedFailPoint': $test->assertInstanceOf(Session::class, $args['session']); $test->configureFailPoint($this->arguments['failPoint'], $args['session']->getServer()); return null; default: throw new LogicException('Unsupported test runner operation: ' . $this->name); } } /** * @throws LogicException if the operation object is unsupported */ private function getResultAssertionType() { switch ($this->object) { case self::OBJECT_CLIENT: return $this->getResultAssertionTypeForClient(); case self::OBJECT_COLLECTION: return $this->getResultAssertionTypeForCollection(); case self::OBJECT_DATABASE: return $this->getResultAssertionTypeForDatabase(); case self::OBJECT_GRIDFS_BUCKET: return ResultExpectation::ASSERT_SAME; case self::OBJECT_SESSION0: case self::OBJECT_SESSION1: case self::OBJECT_TEST_RUNNER: return ResultExpectation::ASSERT_NOTHING; default: throw new LogicException('Unsupported object: ' . $this->object); } } /** * @throws LogicException if the collection operation is unsupported */ private function getResultAssertionTypeForClient() { switch ($this->name) { case 'listDatabases': return ResultExpectation::ASSERT_SAME_DOCUMENTS; case 'watch': return ResultExpectation::ASSERT_SAME_DOCUMENTS; default: throw new LogicException('Unsupported client operation: ' . $this->name); } } /** * @throws LogicException if the collection operation is unsupported */ private function getResultAssertionTypeForCollection() { switch ($this->name) { case 'aggregate': /* Returning a cursor for the $out collection is optional per * the CRUD specification and is not implemented in the library * since we have no concept of lazy cursors. Rely on examining * the output collection rather than the operation result. */ if (is_last_pipeline_operator_write($this->arguments['pipeline'])) { return ResultExpectation::ASSERT_NOTHING; } return ResultExpectation::ASSERT_SAME_DOCUMENTS; case 'bulkWrite': return ResultExpectation::ASSERT_BULKWRITE; case 'count': case 'countDocuments': case 'distinct': case 'estimatedDocumentCount': return ResultExpectation::ASSERT_SAME; case 'deleteMany': case 'deleteOne': return ResultExpectation::ASSERT_DELETE; case 'drop': return ResultExpectation::ASSERT_NOTHING; case 'findOne': case 'findOneAndDelete': case 'findOneAndReplace': case 'findOneAndUpdate': return ResultExpectation::ASSERT_SAME_DOCUMENT; case 'find': return ResultExpectation::ASSERT_SAME_DOCUMENTS; case 'insertMany': return ResultExpectation::ASSERT_INSERTMANY; case 'insertOne': return ResultExpectation::ASSERT_INSERTONE; case 'listIndexes': return ResultExpectation::ASSERT_SAME_DOCUMENTS; case 'mapReduce': return ResultExpectation::ASSERT_SAME_DOCUMENTS; case 'replaceOne': case 'updateMany': case 'updateOne': return ResultExpectation::ASSERT_UPDATE; case 'watch': return ResultExpectation::ASSERT_SAME_DOCUMENTS; default: throw new LogicException('Unsupported collection operation: ' . $this->name); } } /** * @throws LogicException if the database operation is unsupported */ private function getResultAssertionTypeForDatabase() { switch ($this->name) { case 'aggregate': case 'listCollections': return ResultExpectation::ASSERT_SAME_DOCUMENTS; case 'runCommand': return ResultExpectation::ASSERT_MATCHES_DOCUMENT; case 'watch': return ResultExpectation::ASSERT_SAME_DOCUMENTS; default: throw new LogicException('Unsupported database operation: ' . $this->name); } } /** * Prepares a request element for a bulkWrite operation. * * @param stdClass $request * @return array * @throws LogicException if the bulk write request is unsupported */ private function prepareBulkWriteRequest(stdClass $request) { $args = (array) $request->arguments; switch ($request->name) { case 'deleteMany': case 'deleteOne': return [ $request->name => [ $args['filter'], array_diff_key($args, ['filter' => 1]), ], ]; case 'insertOne': return [ 'insertOne' => [ $args['document'] ]]; case 'replaceOne': return [ 'replaceOne' => [ $args['filter'], $args['replacement'], array_diff_key($args, ['filter' => 1, 'replacement' => 1]), ], ]; case 'updateMany': case 'updateOne': return [ $request->name => [ $args['filter'], $args['update'], array_diff_key($args, ['filter' => 1, 'update' => 1]), ], ]; default: throw new LogicException('Unsupported bulk write request: ' . $request->name); } } }