Command.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\mongodb;
  8. use MongoDB\BSON\ObjectID;
  9. use MongoDB\Driver\BulkWrite;
  10. use MongoDB\Driver\Exception\RuntimeException;
  11. use MongoDB\Driver\ReadConcern;
  12. use MongoDB\Driver\ReadPreference;
  13. use MongoDB\Driver\WriteConcern;
  14. use MongoDB\Driver\WriteResult;
  15. use Yii;
  16. use yii\base\InvalidConfigException;
  17. use yii\base\BaseObject;
  18. /**
  19. * Command represents MongoDB statement such as command or query.
  20. *
  21. * A command object is usually created by calling [[Connection::createCommand()]] or [[Database::createCommand()]].
  22. * The statement it represents can be set via the [[document]] property.
  23. *
  24. * To execute a non-query command, such as 'listIndexes', 'count', 'distinct' and so on, call [[execute()]].
  25. * For example:
  26. *
  27. * ```php
  28. * $result = Yii::$app->mongodb->createCommand(['listIndexes' => 'some_collection'])->execute();
  29. * ```
  30. *
  31. * To execute a 'find' command, which return cursor, call [[query()]].
  32. * For example:
  33. *
  34. * ```php
  35. * $cursor = Yii::$app->mongodb->createCommand(['projection' => ['name' => true]])->query('some_collection');
  36. * ```
  37. *
  38. * To execute batch (bulk) operations, call [[executeBatch()]].
  39. * For example:
  40. *
  41. * ```php
  42. * Yii::$app->mongodb->createCommand()
  43. * ->addInsert(['name' => 'new'])
  44. * ->addUpdate(['name' => 'existing'], ['name' => 'updated'])
  45. * ->addDelete(['name' => 'old'])
  46. * ->executeBatch('some_collection');
  47. * ```
  48. *
  49. * @property ReadConcern|string $readConcern Read concern to be used in this command.
  50. * @property ReadPreference $readPreference Read preference. Note that the type of this property differs in
  51. * getter and setter. See [[getReadPreference()]] and [[setReadPreference()]] for details.
  52. * @property WriteConcern|null $writeConcern Write concern to be used in this command. Note that the type of
  53. * this property differs in getter and setter. See [[getWriteConcern()]] and [[setWriteConcern()]] for details.
  54. *
  55. * @author Paul Klimov <klimov.paul@gmail.com>
  56. * @since 2.1
  57. */
  58. class Command extends BaseObject
  59. {
  60. /**
  61. * @var Connection the MongoDB connection that this command is associated with.
  62. */
  63. public $db;
  64. /**
  65. * @var string name of the database that this command is associated with.
  66. */
  67. public $databaseName;
  68. /**
  69. * @var array command document contents.
  70. */
  71. public $document = [];
  72. /**
  73. * @var ReadPreference|int|string|null command read preference.
  74. */
  75. private $_readPreference;
  76. /**
  77. * @var WriteConcern|int|string|null write concern to be used by this command.
  78. */
  79. private $_writeConcern;
  80. /**
  81. * @var ReadConcern|string read concern to be used by this command
  82. */
  83. private $_readConcern;
  84. /**
  85. * Returns read preference for this command.
  86. * @return ReadPreference read preference.
  87. */
  88. public function getReadPreference()
  89. {
  90. if (!is_object($this->_readPreference)) {
  91. if ($this->_readPreference === null) {
  92. $this->_readPreference = $this->db->manager->getReadPreference();
  93. } elseif (is_scalar($this->_readPreference)) {
  94. $this->_readPreference = new ReadPreference($this->_readPreference);
  95. }
  96. }
  97. return $this->_readPreference;
  98. }
  99. /**
  100. * Sets read preference for this command.
  101. * @param ReadPreference|int|string|null $readPreference read reference, it can be specified as
  102. * instance of [[ReadPreference]] or scalar mode value, for example: `ReadPreference::RP_PRIMARY`.
  103. * @return $this self reference.
  104. */
  105. public function setReadPreference($readPreference)
  106. {
  107. $this->_readPreference = $readPreference;
  108. return $this;
  109. }
  110. /**
  111. * Returns write concern for this command.
  112. * @return WriteConcern|null write concern to be used in this command.
  113. */
  114. public function getWriteConcern()
  115. {
  116. if ($this->_writeConcern !== null) {
  117. if (is_scalar($this->_writeConcern)) {
  118. $this->_writeConcern = new WriteConcern($this->_writeConcern);
  119. }
  120. }
  121. return $this->_writeConcern;
  122. }
  123. /**
  124. * Sets write concern for this command.
  125. * @param WriteConcern|int|string|null $writeConcern write concern, it can be an instance of [[WriteConcern]]
  126. * or its scalar mode value, for example: `majority`.
  127. * @return $this self reference
  128. */
  129. public function setWriteConcern($writeConcern)
  130. {
  131. $this->_writeConcern = $writeConcern;
  132. return $this;
  133. }
  134. /**
  135. * Retuns read concern for this command.
  136. * @return ReadConcern|string read concern to be used in this command.
  137. */
  138. public function getReadConcern()
  139. {
  140. if ($this->_readConcern !== null) {
  141. if (is_scalar($this->_readConcern)) {
  142. $this->_readConcern = new ReadConcern($this->_readConcern);
  143. }
  144. }
  145. return $this->_readConcern;
  146. }
  147. /**
  148. * Sets read concern for this command.
  149. * @param ReadConcern|string $readConcern read concern, it can be an instance of [[ReadConcern]] or
  150. * scalar level value, for example: 'local'.
  151. * @return $this self reference
  152. */
  153. public function setReadConcern($readConcern)
  154. {
  155. $this->_readConcern = $readConcern;
  156. return $this;
  157. }
  158. /**
  159. * Executes this command.
  160. * @return \MongoDB\Driver\Cursor result cursor.
  161. * @throws Exception on failure.
  162. */
  163. public function execute()
  164. {
  165. $databaseName = $this->databaseName === null ? $this->db->defaultDatabaseName : $this->databaseName;
  166. $token = $this->log([$databaseName, 'command'], $this->document, __METHOD__);
  167. try {
  168. $this->beginProfile($token, __METHOD__);
  169. $this->db->open();
  170. $mongoCommand = new \MongoDB\Driver\Command($this->document);
  171. $cursor = $this->db->manager->executeCommand($databaseName, $mongoCommand, $this->getReadPreference());
  172. $cursor->setTypeMap($this->db->typeMap);
  173. $this->endProfile($token, __METHOD__);
  174. } catch (RuntimeException $e) {
  175. $this->endProfile($token, __METHOD__);
  176. throw new Exception($e->getMessage(), $e->getCode(), $e);
  177. }
  178. return $cursor;
  179. }
  180. /**
  181. * Execute commands batch (bulk).
  182. * @param string $collectionName collection name.
  183. * @param array $options batch options.
  184. * @return array array of 2 elements:
  185. *
  186. * - 'insertedIds' - contains inserted IDs.
  187. * - 'result' - [[\MongoDB\Driver\WriteResult]] instance.
  188. *
  189. * @throws Exception on failure.
  190. * @throws InvalidConfigException on invalid [[document]] format.
  191. */
  192. public function executeBatch($collectionName, $options = [])
  193. {
  194. $databaseName = $this->databaseName === null ? $this->db->defaultDatabaseName : $this->databaseName;
  195. $token = $this->log([$databaseName, $collectionName, 'bulkWrite'], $this->document, __METHOD__);
  196. try {
  197. $this->beginProfile($token, __METHOD__);
  198. $batch = new BulkWrite($options);
  199. $insertedIds = [];
  200. foreach ($this->document as $key => $operation) {
  201. switch ($operation['type']) {
  202. case 'insert':
  203. $insertedIds[$key] = $batch->insert($operation['document']);
  204. break;
  205. case 'update':
  206. $batch->update($operation['condition'], $operation['document'], $operation['options']);
  207. break;
  208. case 'delete':
  209. $batch->delete($operation['condition'], isset($operation['options']) ? $operation['options'] : []);
  210. break;
  211. default:
  212. throw new InvalidConfigException("Unsupported batch operation type '{$operation['type']}'");
  213. }
  214. }
  215. $this->db->open();
  216. $writeResult = $this->db->manager->executeBulkWrite($databaseName . '.' . $collectionName, $batch, $this->getWriteConcern());
  217. $this->endProfile($token, __METHOD__);
  218. } catch (RuntimeException $e) {
  219. $this->endProfile($token, __METHOD__);
  220. throw new Exception($e->getMessage(), $e->getCode(), $e);
  221. }
  222. return [
  223. 'insertedIds' => $insertedIds,
  224. 'result' => $writeResult,
  225. ];
  226. }
  227. /**
  228. * Executes this command as a mongo query
  229. * @param string $collectionName collection name
  230. * @param array $options query options.
  231. * @return \MongoDB\Driver\Cursor result cursor.
  232. * @throws Exception on failure
  233. */
  234. public function query($collectionName, $options = [])
  235. {
  236. $databaseName = $this->databaseName === null ? $this->db->defaultDatabaseName : $this->databaseName;
  237. $token = $this->log(
  238. 'find',
  239. array_merge(
  240. [
  241. 'ns' => $databaseName . '.' . $collectionName,
  242. 'filter' => $this->document,
  243. ],
  244. $options
  245. ),
  246. __METHOD__
  247. );
  248. $readConcern = $this->getReadConcern();
  249. if ($readConcern !== null) {
  250. $options['readConcern'] = $readConcern;
  251. }
  252. try {
  253. $this->beginProfile($token, __METHOD__);
  254. $query = new \MongoDB\Driver\Query($this->document, $options);
  255. $this->db->open();
  256. $cursor = $this->db->manager->executeQuery($databaseName . '.' . $collectionName, $query, $this->getReadPreference());
  257. $cursor->setTypeMap($this->db->typeMap);
  258. $this->endProfile($token, __METHOD__);
  259. } catch (RuntimeException $e) {
  260. $this->endProfile($token, __METHOD__);
  261. throw new Exception($e->getMessage(), $e->getCode(), $e);
  262. }
  263. return $cursor;
  264. }
  265. /**
  266. * Drops database associated with this command.
  267. * @return bool whether operation was successful.
  268. */
  269. public function dropDatabase()
  270. {
  271. $this->document = $this->db->getQueryBuilder()->dropDatabase();
  272. $result = current($this->execute()->toArray());
  273. return $result['ok'] > 0;
  274. }
  275. /**
  276. * Creates new collection in database associated with this command.s
  277. * @param string $collectionName collection name
  278. * @param array $options collection options in format: "name" => "value"
  279. * @return bool whether operation was successful.
  280. */
  281. public function createCollection($collectionName, array $options = [])
  282. {
  283. $this->document = $this->db->getQueryBuilder()->createCollection($collectionName, $options);
  284. $result = current($this->execute()->toArray());
  285. return $result['ok'] > 0;
  286. }
  287. /**
  288. * Drops specified collection.
  289. * @param string $collectionName name of the collection to be dropped.
  290. * @return bool whether operation was successful.
  291. */
  292. public function dropCollection($collectionName)
  293. {
  294. $this->document = $this->db->getQueryBuilder()->dropCollection($collectionName);
  295. $result = current($this->execute()->toArray());
  296. return $result['ok'] > 0;
  297. }
  298. /**
  299. * Creates indexes in the collection.
  300. * @param string $collectionName collection name.
  301. * @param array[] $indexes indexes specification. Each specification should be an array in format: optionName => value
  302. * The main options are:
  303. *
  304. * - keys: array, column names with sort order, to be indexed. This option is mandatory.
  305. * - unique: bool, whether to create unique index.
  306. * - name: string, the name of the index, if not set it will be generated automatically.
  307. * - background: bool, whether to bind index in the background.
  308. * - sparse: bool, whether index should reference only documents with the specified field.
  309. *
  310. * See [[https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/#options-for-all-index-types]]
  311. * for the full list of options.
  312. * @return bool whether operation was successful.
  313. */
  314. public function createIndexes($collectionName, $indexes)
  315. {
  316. $this->document = $this->db->getQueryBuilder()->createIndexes($this->databaseName, $collectionName, $indexes);
  317. $result = current($this->execute()->toArray());
  318. return $result['ok'] > 0;
  319. }
  320. /**
  321. * Drops collection indexes by name.
  322. * @param string $collectionName collection name.
  323. * @param string $indexes wildcard for name of the indexes to be dropped.
  324. * @return array result data.
  325. */
  326. public function dropIndexes($collectionName, $indexes)
  327. {
  328. $this->document = $this->db->getQueryBuilder()->dropIndexes($collectionName, $indexes);
  329. return current($this->execute()->toArray());
  330. }
  331. /**
  332. * Returns information about current collection indexes.
  333. * @param string $collectionName collection name
  334. * @param array $options list of options in format: optionName => optionValue.
  335. * @return array list of indexes info.
  336. * @throws Exception on failure.
  337. */
  338. public function listIndexes($collectionName, $options = [])
  339. {
  340. $this->document = $this->db->getQueryBuilder()->listIndexes($collectionName, $options);
  341. try {
  342. $cursor = $this->execute();
  343. } catch (Exception $e) {
  344. // The server may return an error if the collection does not exist.
  345. $notFoundCodes = [
  346. 26, // namespace not found
  347. 60 // database not found
  348. ];
  349. if (in_array($e->getCode(), $notFoundCodes, true)) {
  350. return [];
  351. }
  352. throw $e;
  353. }
  354. return $cursor->toArray();
  355. }
  356. /**
  357. * Counts records in specified collection.
  358. * @param string $collectionName collection name
  359. * @param array $condition filter condition
  360. * @param array $options list of options in format: optionName => optionValue.
  361. * @return int records count
  362. */
  363. public function count($collectionName, $condition = [], $options = [])
  364. {
  365. $this->document = $this->db->getQueryBuilder()->count($collectionName, $condition, $options);
  366. $result = current($this->execute()->toArray());
  367. return $result['n'];
  368. }
  369. /**
  370. * Adds the insert operation to the batch command.
  371. * @param array $document document to be inserted
  372. * @return $this self reference.
  373. * @see executeBatch()
  374. */
  375. public function addInsert($document)
  376. {
  377. $this->document[] = [
  378. 'type' => 'insert',
  379. 'document' => $document,
  380. ];
  381. return $this;
  382. }
  383. /**
  384. * Adds the update operation to the batch command.
  385. * @param array $condition filter condition
  386. * @param array $document data to be updated
  387. * @param array $options update options.
  388. * @return $this self reference.
  389. * @see executeBatch()
  390. */
  391. public function addUpdate($condition, $document, $options = [])
  392. {
  393. $options = array_merge(
  394. [
  395. 'multi' => true,
  396. 'upsert' => false,
  397. ],
  398. $options
  399. );
  400. if ($options['multi']) {
  401. $keys = array_keys($document);
  402. if (!empty($keys) && strncmp('$', $keys[0], 1) !== 0) {
  403. $document = ['$set' => $document];
  404. }
  405. }
  406. $this->document[] = [
  407. 'type' => 'update',
  408. 'condition' => $this->db->getQueryBuilder()->buildCondition($condition),
  409. 'document' => $document,
  410. 'options' => $options,
  411. ];
  412. return $this;
  413. }
  414. /**
  415. * Adds the delete operation to the batch command.
  416. * @param array $condition filter condition.
  417. * @param array $options delete options.
  418. * @return $this self reference.
  419. * @see executeBatch()
  420. */
  421. public function addDelete($condition, $options = [])
  422. {
  423. $this->document[] = [
  424. 'type' => 'delete',
  425. 'condition' => $this->db->getQueryBuilder()->buildCondition($condition),
  426. 'options' => $options,
  427. ];
  428. return $this;
  429. }
  430. /**
  431. * Inserts new document into collection.
  432. * @param string $collectionName collection name
  433. * @param array $document document content
  434. * @param array $options list of options in format: optionName => optionValue.
  435. * @return ObjectID|bool inserted record ID, `false` - on failure.
  436. */
  437. public function insert($collectionName, $document, $options = [])
  438. {
  439. $this->document = [];
  440. $this->addInsert($document);
  441. $result = $this->executeBatch($collectionName, $options);
  442. if ($result['result']->getInsertedCount() < 1) {
  443. return false;
  444. }
  445. return reset($result['insertedIds']);
  446. }
  447. /**
  448. * Inserts batch of new documents into collection.
  449. * @param string $collectionName collection name
  450. * @param array[] $documents documents list
  451. * @param array $options list of options in format: optionName => optionValue.
  452. * @return array|false list of inserted IDs, `false` on failure.
  453. */
  454. public function batchInsert($collectionName, $documents, $options = [])
  455. {
  456. $this->document = [];
  457. foreach ($documents as $key => $document) {
  458. $this->document[$key] = [
  459. 'type' => 'insert',
  460. 'document' => $document
  461. ];
  462. }
  463. $result = $this->executeBatch($collectionName, $options);
  464. if ($result['result']->getInsertedCount() < 1) {
  465. return false;
  466. }
  467. return $result['insertedIds'];
  468. }
  469. /**
  470. * Update existing documents in the collection.
  471. * @param string $collectionName collection name
  472. * @param array $condition filter condition
  473. * @param array $document data to be updated.
  474. * @param array $options update options.
  475. * @return WriteResult write result.
  476. */
  477. public function update($collectionName, $condition, $document, $options = [])
  478. {
  479. $batchOptions = [];
  480. foreach (['bypassDocumentValidation'] as $name) {
  481. if (isset($options[$name])) {
  482. $batchOptions[$name] = $options[$name];
  483. unset($options[$name]);
  484. }
  485. }
  486. $this->document = [];
  487. $this->addUpdate($condition, $document, $options);
  488. $result = $this->executeBatch($collectionName, $batchOptions);
  489. return $result['result'];
  490. }
  491. /**
  492. * Removes documents from the collection.
  493. * @param string $collectionName collection name.
  494. * @param array $condition filter condition.
  495. * @param array $options delete options.
  496. * @return WriteResult write result.
  497. */
  498. public function delete($collectionName, $condition, $options = [])
  499. {
  500. $batchOptions = [];
  501. foreach (['bypassDocumentValidation'] as $name) {
  502. if (isset($options[$name])) {
  503. $batchOptions[$name] = $options[$name];
  504. unset($options[$name]);
  505. }
  506. }
  507. $this->document = [];
  508. $this->addDelete($condition, $options);
  509. $result = $this->executeBatch($collectionName, $batchOptions);
  510. return $result['result'];
  511. }
  512. /**
  513. * Performs find query.
  514. * @param string $collectionName collection name
  515. * @param array $condition filter condition
  516. * @param array $options query options.
  517. * @return \MongoDB\Driver\Cursor result cursor.
  518. */
  519. public function find($collectionName, $condition, $options = [])
  520. {
  521. $queryBuilder = $this->db->getQueryBuilder();
  522. $this->document = $queryBuilder->buildCondition($condition);
  523. if (isset($options['projection'])) {
  524. $options['projection'] = $queryBuilder->buildSelectFields($options['projection']);
  525. }
  526. if (isset($options['sort'])) {
  527. $options['sort'] = $queryBuilder->buildSortFields($options['sort']);
  528. }
  529. if (array_key_exists('limit', $options)) {
  530. if ($options['limit'] === null || !ctype_digit((string) $options['limit'])) {
  531. unset($options['limit']);
  532. } else {
  533. $options['limit'] = (int)$options['limit'];
  534. }
  535. }
  536. if (array_key_exists('skip', $options)) {
  537. if ($options['skip'] === null || !ctype_digit((string) $options['skip'])) {
  538. unset($options['skip']);
  539. } else {
  540. $options['skip'] = (int)$options['skip'];
  541. }
  542. }
  543. return $this->query($collectionName, $options);
  544. }
  545. /**
  546. * Updates a document and returns it.
  547. * @param $collectionName
  548. * @param array $condition query condition
  549. * @param array $update update criteria
  550. * @param array $options list of options in format: optionName => optionValue.
  551. * @return array|null the original document, or the modified document when $options['new'] is set.
  552. */
  553. public function findAndModify($collectionName, $condition = [], $update = [], $options = [])
  554. {
  555. $this->document = $this->db->getQueryBuilder()->findAndModify($collectionName, $condition, $update, $options);
  556. $cursor = $this->execute();
  557. $result = current($cursor->toArray());
  558. if (!isset($result['value'])) {
  559. return null;
  560. }
  561. return $result['value'];
  562. }
  563. /**
  564. * Returns a list of distinct values for the given column across a collection.
  565. * @param string $collectionName collection name.
  566. * @param string $fieldName field name to use.
  567. * @param array $condition query parameters.
  568. * @param array $options list of options in format: optionName => optionValue.
  569. * @return array array of distinct values, or "false" on failure.
  570. */
  571. public function distinct($collectionName, $fieldName, $condition = [], $options = [])
  572. {
  573. $this->document = $this->db->getQueryBuilder()->distinct($collectionName, $fieldName, $condition, $options);
  574. $cursor = $this->execute();
  575. $result = current($cursor->toArray());
  576. if (!isset($result['values']) || !is_array($result['values'])) {
  577. return false;
  578. }
  579. return $result['values'];
  580. }
  581. /**
  582. * Performs aggregation using MongoDB "group" command.
  583. * @param string $collectionName collection name.
  584. * @param mixed $keys fields to group by. If an array or non-code object is passed,
  585. * it will be the key used to group results. If instance of [[\MongoDB\BSON\Javascript]] passed,
  586. * it will be treated as a function that returns the key to group by.
  587. * @param array $initial Initial value of the aggregation counter object.
  588. * @param \MongoDB\BSON\Javascript|string $reduce function that takes two arguments (the current
  589. * document and the aggregation to this point) and does the aggregation.
  590. * Argument will be automatically cast to [[\MongoDB\BSON\Javascript]].
  591. * @param array $options optional parameters to the group command. Valid options include:
  592. * - condition - criteria for including a document in the aggregation.
  593. * - finalize - function called once per unique key that takes the final output of the reduce function.
  594. * @return array the result of the aggregation.
  595. */
  596. public function group($collectionName, $keys, $initial, $reduce, $options = [])
  597. {
  598. $this->document = $this->db->getQueryBuilder()->group($collectionName, $keys, $initial, $reduce, $options);
  599. $cursor = $this->execute();
  600. $result = current($cursor->toArray());
  601. return $result['retval'];
  602. }
  603. /**
  604. * Performs MongoDB "map-reduce" command.
  605. * @param string $collectionName collection name.
  606. * @param \MongoDB\BSON\Javascript|string $map function, which emits map data from collection.
  607. * Argument will be automatically cast to [[\MongoDB\BSON\Javascript]].
  608. * @param \MongoDB\BSON\Javascript|string $reduce function that takes two arguments (the map key
  609. * and the map values) and does the aggregation.
  610. * Argument will be automatically cast to [[\MongoDB\BSON\Javascript]].
  611. * @param string|array $out output collection name. It could be a string for simple output
  612. * ('outputCollection'), or an array for parametrized output (['merge' => 'outputCollection']).
  613. * You can pass ['inline' => true] to fetch the result at once without temporary collection usage.
  614. * @param array $condition filter condition for including a document in the aggregation.
  615. * @param array $options additional optional parameters to the mapReduce command. Valid options include:
  616. *
  617. * - sort: array, key to sort the input documents. The sort key must be in an existing index for this collection.
  618. * - limit: int, the maximum number of documents to return in the collection.
  619. * - finalize: \MongoDB\BSON\Javascript|string, function, which follows the reduce method and modifies the output.
  620. * - scope: array, specifies global variables that are accessible in the map, reduce and finalize functions.
  621. * - jsMode: bool, specifies whether to convert intermediate data into BSON format between the execution of the map and reduce functions.
  622. * - verbose: bool, specifies whether to include the timing information in the result information.
  623. *
  624. * @return string|array the map reduce output collection name or output results.
  625. */
  626. public function mapReduce($collectionName, $map, $reduce, $out, $condition = [], $options = [])
  627. {
  628. $this->document = $this->db->getQueryBuilder()->mapReduce($collectionName, $map, $reduce, $out, $condition, $options);
  629. $cursor = $this->execute();
  630. $result = current($cursor->toArray());
  631. return array_key_exists('results', $result) ? $result['results'] : $result['result'];
  632. }
  633. /**
  634. * Performs aggregation using MongoDB Aggregation Framework.
  635. * In case 'cursor' option is specified [[\MongoDB\Driver\Cursor]] instance is returned,
  636. * otherwise - an array of aggregation results.
  637. * @param string $collectionName collection name
  638. * @param array $pipelines list of pipeline operators.
  639. * @param array $options optional parameters.
  640. * @return array|\MongoDB\Driver\Cursor aggregation result.
  641. */
  642. public function aggregate($collectionName, $pipelines, $options = [])
  643. {
  644. if (empty($options['cursor'])) {
  645. $returnCursor = false;
  646. $options['cursor'] = new \stdClass();
  647. } else {
  648. $returnCursor = true;
  649. }
  650. $this->document = $this->db->getQueryBuilder()->aggregate($collectionName, $pipelines, $options);
  651. $cursor = $this->execute();
  652. if ($returnCursor) {
  653. return $cursor;
  654. }
  655. return $cursor->toArray();
  656. }
  657. /**
  658. * Return an explanation of the query, often useful for optimization and debugging.
  659. * @param string $collectionName collection name
  660. * @param array $query query document.
  661. * @return array explanation of the query.
  662. */
  663. public function explain($collectionName, $query)
  664. {
  665. $this->document = $this->db->getQueryBuilder()->explain($collectionName, $query);
  666. $cursor = $this->execute();
  667. return current($cursor->toArray());
  668. }
  669. /**
  670. * Returns the list of available databases.
  671. * @param array $condition filter condition.
  672. * @param array $options options list.
  673. * @return array database information
  674. */
  675. public function listDatabases($condition = [], $options = [])
  676. {
  677. if ($this->databaseName === null) {
  678. $this->databaseName = 'admin';
  679. }
  680. $this->document = $this->db->getQueryBuilder()->listDatabases($condition, $options);
  681. $cursor = $this->execute();
  682. $result = current($cursor->toArray());
  683. if (empty($result['databases'])) {
  684. return [];
  685. }
  686. return $result['databases'];
  687. }
  688. /**
  689. * Returns the list of available collections.
  690. * @param array $condition filter condition.
  691. * @param array $options options list.
  692. * @return array collections information.
  693. */
  694. public function listCollections($condition = [], $options = [])
  695. {
  696. $this->document = $this->db->getQueryBuilder()->listCollections($condition, $options);
  697. $cursor = $this->execute();
  698. return $cursor->toArray();
  699. }
  700. // Logging :
  701. /**
  702. * Logs the command data if logging is enabled at [[db]].
  703. * @param array|string $namespace command namespace.
  704. * @param array $data command data.
  705. * @param string $category log category
  706. * @return string|false log token, `false` if log is not enabled.
  707. */
  708. protected function log($namespace, $data, $category)
  709. {
  710. if ($this->db->enableLogging) {
  711. $token = $this->db->getLogBuilder()->generateToken($namespace, $data);
  712. Yii::info($token, $category);
  713. return $token;
  714. }
  715. return false;
  716. }
  717. /**
  718. * Marks the beginning of a code block for profiling.
  719. * @param string $token token for the code block
  720. * @param string $category the category of this log message
  721. * @see endProfile()
  722. */
  723. protected function beginProfile($token, $category)
  724. {
  725. if ($token !== false && $this->db->enableProfiling) {
  726. Yii::beginProfile($token, $category);
  727. }
  728. }
  729. /**
  730. * Marks the end of a code block for profiling.
  731. * @param string $token token for the code block
  732. * @param string $category the category of this log message
  733. * @see beginProfile()
  734. */
  735. protected function endProfile($token, $category)
  736. {
  737. if ($token !== false && $this->db->enableProfiling) {
  738. Yii::endProfile($token, $category);
  739. }
  740. }
  741. }