Differences Between: [Versions 310 and 400] [Versions 39 and 400] [Versions 400 and 401]
1 <?php 2 /* 3 * Copyright 2015-2017 MongoDB, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 namespace MongoDB\Operation; 19 20 use ArrayIterator; 21 use MongoDB\BSON\JavascriptInterface; 22 use MongoDB\Driver\Command; 23 use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 24 use MongoDB\Driver\ReadConcern; 25 use MongoDB\Driver\ReadPreference; 26 use MongoDB\Driver\Server; 27 use MongoDB\Driver\Session; 28 use MongoDB\Driver\WriteConcern; 29 use MongoDB\Exception\InvalidArgumentException; 30 use MongoDB\Exception\UnexpectedValueException; 31 use MongoDB\Exception\UnsupportedException; 32 use MongoDB\MapReduceResult; 33 use stdClass; 34 use function current; 35 use function is_array; 36 use function is_bool; 37 use function is_integer; 38 use function is_object; 39 use function is_string; 40 use function MongoDB\create_field_path_type_map; 41 use function MongoDB\is_mapreduce_output_inline; 42 use function MongoDB\server_supports_feature; 43 use function trigger_error; 44 use const E_USER_DEPRECATED; 45 46 /** 47 * Operation for the mapReduce command. 48 * 49 * @api 50 * @see \MongoDB\Collection::mapReduce() 51 * @see https://docs.mongodb.com/manual/reference/command/mapReduce/ 52 */ 53 class MapReduce implements Executable 54 { 55 /** @var integer */ 56 private static $wireVersionForCollation = 5; 57 58 /** @var integer */ 59 private static $wireVersionForDocumentLevelValidation = 4; 60 61 /** @var integer */ 62 private static $wireVersionForReadConcern = 4; 63 64 /** @var integer */ 65 private static $wireVersionForWriteConcern = 4; 66 67 /** @var string */ 68 private $databaseName; 69 70 /** @var string */ 71 private $collectionName; 72 73 /** @var JavascriptInterface */ 74 private $map; 75 76 /** @var JavascriptInterface */ 77 private $reduce; 78 79 /** @var array|object|string */ 80 private $out; 81 82 /** @var array */ 83 private $options; 84 85 /** 86 * Constructs a mapReduce command. 87 * 88 * Required arguments: 89 * 90 * * map (MongoDB\BSON\Javascript): A JavaScript function that associates 91 * or "maps" a value with a key and emits the key and value pair. 92 * 93 * Passing a Javascript instance with a scope is deprecated. Put all 94 * scope variables in the "scope" option of the MapReduce operation. 95 * 96 * * reduce (MongoDB\BSON\Javascript): A JavaScript function that "reduces" 97 * to a single object all the values associated with a particular key. 98 * 99 * Passing a Javascript instance with a scope is deprecated. Put all 100 * scope variables in the "scope" option of the MapReduce operation. 101 * 102 * * out (string|document): Specifies where to output the result of the 103 * map-reduce operation. You can either output to a collection or return 104 * the result inline. On a primary member of a replica set you can output 105 * either to a collection or inline, but on a secondary, only inline 106 * output is possible. 107 * 108 * Supported options: 109 * 110 * * bypassDocumentValidation (boolean): If true, allows the write to 111 * circumvent document level validation. This only applies when results 112 * are output to a collection. 113 * 114 * For servers < 3.2, this option is ignored as document level validation 115 * is not available. 116 * 117 * * collation (document): Collation specification. 118 * 119 * This is not supported for server versions < 3.4 and will result in an 120 * exception at execution time if used. 121 * 122 * * finalize (MongoDB\BSON\JavascriptInterface): Follows the reduce method 123 * and modifies the output. 124 * 125 * Passing a Javascript instance with a scope is deprecated. Put all 126 * scope variables in the "scope" option of the MapReduce operation. 127 * 128 * * jsMode (boolean): Specifies whether to convert intermediate data into 129 * BSON format between the execution of the map and reduce functions. 130 * 131 * * limit (integer): Specifies a maximum number of documents for the input 132 * into the map function. 133 * 134 * * maxTimeMS (integer): The maximum amount of time to allow the query to 135 * run. 136 * 137 * * query (document): Specifies the selection criteria using query 138 * operators for determining the documents input to the map function. 139 * 140 * * readConcern (MongoDB\Driver\ReadConcern): Read concern. This is not 141 * supported when results are returned inline. 142 * 143 * This is not supported for server versions < 3.2 and will result in an 144 * exception at execution time if used. 145 * 146 * * readPreference (MongoDB\Driver\ReadPreference): Read preference. 147 * 148 * This option is ignored if results are output to a collection. 149 * 150 * * scope (document): Specifies global variables that are accessible in 151 * the map, reduce and finalize functions. 152 * 153 * * session (MongoDB\Driver\Session): Client session. 154 * 155 * Sessions are not supported for server versions < 3.6. 156 * 157 * * sort (document): Sorts the input documents. This option is useful for 158 * optimization. For example, specify the sort key to be the same as the 159 * emit key so that there are fewer reduce operations. The sort key must 160 * be in an existing index for this collection. 161 * 162 * * typeMap (array): Type map for BSON deserialization. This will be 163 * applied to the returned Cursor (it is not sent to the server). 164 * 165 * * verbose (boolean): Specifies whether to include the timing information 166 * in the result information. 167 * 168 * * writeConcern (MongoDB\Driver\WriteConcern): Write concern. This only 169 * applies when results are output to a collection. 170 * 171 * This is not supported for server versions < 3.4 and will result in an 172 * exception at execution time if used. 173 * 174 * @param string $databaseName Database name 175 * @param string $collectionName Collection name 176 * @param JavascriptInterface $map Map function 177 * @param JavascriptInterface $reduce Reduce function 178 * @param string|array|object $out Output specification 179 * @param array $options Command options 180 * @throws InvalidArgumentException for parameter/option parsing errors 181 */ 182 public function __construct($databaseName, $collectionName, JavascriptInterface $map, JavascriptInterface $reduce, $out, array $options = []) 183 { 184 if (! is_string($out) && ! is_array($out) && ! is_object($out)) { 185 throw InvalidArgumentException::invalidType('$out', $out, 'string or array or object'); 186 } 187 188 if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) { 189 throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean'); 190 } 191 192 if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) { 193 throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object'); 194 } 195 196 if (isset($options['finalize']) && ! $options['finalize'] instanceof JavascriptInterface) { 197 throw InvalidArgumentException::invalidType('"finalize" option', $options['finalize'], JavascriptInterface::class); 198 } 199 200 if (isset($options['jsMode']) && ! is_bool($options['jsMode'])) { 201 throw InvalidArgumentException::invalidType('"jsMode" option', $options['jsMode'], 'boolean'); 202 } 203 204 if (isset($options['limit']) && ! is_integer($options['limit'])) { 205 throw InvalidArgumentException::invalidType('"limit" option', $options['limit'], 'integer'); 206 } 207 208 if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { 209 throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); 210 } 211 212 if (isset($options['query']) && ! is_array($options['query']) && ! is_object($options['query'])) { 213 throw InvalidArgumentException::invalidType('"query" option', $options['query'], 'array or object'); 214 } 215 216 if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { 217 throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); 218 } 219 220 if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { 221 throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); 222 } 223 224 if (isset($options['scope']) && ! is_array($options['scope']) && ! is_object($options['scope'])) { 225 throw InvalidArgumentException::invalidType('"scope" option', $options['scope'], 'array or object'); 226 } 227 228 if (isset($options['session']) && ! $options['session'] instanceof Session) { 229 throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); 230 } 231 232 if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) { 233 throw InvalidArgumentException::invalidType('"sort" option', $options['sort'], 'array or object'); 234 } 235 236 if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { 237 throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); 238 } 239 240 if (isset($options['verbose']) && ! is_bool($options['verbose'])) { 241 throw InvalidArgumentException::invalidType('"verbose" option', $options['verbose'], 'boolean'); 242 } 243 244 if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { 245 throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); 246 } 247 248 if (isset($options['readConcern']) && $options['readConcern']->isDefault()) { 249 unset($options['readConcern']); 250 } 251 252 if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { 253 unset($options['writeConcern']); 254 } 255 256 // Handle deprecation of CodeWScope 257 if ($map->getScope() !== null) { 258 @trigger_error('Use of Javascript with scope in "$map" argument for MapReduce is deprecated. Put all scope variables in the "scope" option of the MapReduce operation.', E_USER_DEPRECATED); 259 } 260 261 if ($reduce->getScope() !== null) { 262 @trigger_error('Use of Javascript with scope in "$reduce" argument for MapReduce is deprecated. Put all scope variables in the "scope" option of the MapReduce operation.', E_USER_DEPRECATED); 263 } 264 265 if (isset($options['finalize']) && $options['finalize']->getScope() !== null) { 266 @trigger_error('Use of Javascript with scope in "finalize" option for MapReduce is deprecated. Put all scope variables in the "scope" option of the MapReduce operation.', E_USER_DEPRECATED); 267 } 268 269 $this->checkOutDeprecations($out); 270 271 $this->databaseName = (string) $databaseName; 272 $this->collectionName = (string) $collectionName; 273 $this->map = $map; 274 $this->reduce = $reduce; 275 $this->out = $out; 276 $this->options = $options; 277 } 278 279 /** 280 * Execute the operation. 281 * 282 * @see Executable::execute() 283 * @param Server $server 284 * @return MapReduceResult 285 * @throws UnexpectedValueException if the command response was malformed 286 * @throws UnsupportedException if collation, read concern, or write concern is used and unsupported 287 * @throws DriverRuntimeException for other driver errors (e.g. connection errors) 288 */ 289 public function execute(Server $server) 290 { 291 if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) { 292 throw UnsupportedException::collationNotSupported(); 293 } 294 295 if (isset($this->options['readConcern']) && ! server_supports_feature($server, self::$wireVersionForReadConcern)) { 296 throw UnsupportedException::readConcernNotSupported(); 297 } 298 299 if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) { 300 throw UnsupportedException::writeConcernNotSupported(); 301 } 302 303 $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); 304 if ($inTransaction) { 305 if (isset($this->options['readConcern'])) { 306 throw UnsupportedException::readConcernNotSupportedInTransaction(); 307 } 308 if (isset($this->options['writeConcern'])) { 309 throw UnsupportedException::writeConcernNotSupportedInTransaction(); 310 } 311 } 312 313 $hasOutputCollection = ! is_mapreduce_output_inline($this->out); 314 315 $command = $this->createCommand($server); 316 $options = $this->createOptions($hasOutputCollection); 317 318 /* If the mapReduce operation results in a write, use 319 * executeReadWriteCommand to ensure we're handling the writeConcern 320 * option. 321 * In other cases, we use executeCommand as this will prevent the 322 * mapReduce operation from being retried when retryReads is enabled. 323 * See https://github.com/mongodb/specifications/blob/master/source/retryable-reads/retryable-reads.rst#unsupported-read-operations. */ 324 $cursor = $hasOutputCollection 325 ? $server->executeReadWriteCommand($this->databaseName, $command, $options) 326 : $server->executeCommand($this->databaseName, $command, $options); 327 328 if (isset($this->options['typeMap']) && ! $hasOutputCollection) { 329 $cursor->setTypeMap(create_field_path_type_map($this->options['typeMap'], 'results.$')); 330 } 331 332 $result = current($cursor->toArray()); 333 334 $getIterator = $this->createGetIteratorCallable($result, $server); 335 336 return new MapReduceResult($getIterator, $result); 337 } 338 339 /** 340 * @param string|array|object $out 341 * @return void 342 */ 343 private function checkOutDeprecations($out) 344 { 345 if (is_string($out)) { 346 return; 347 } 348 349 $out = (array) $out; 350 351 if (isset($out['nonAtomic']) && ! $out['nonAtomic']) { 352 @trigger_error('Specifying false for "out.nonAtomic" is deprecated.', E_USER_DEPRECATED); 353 } 354 355 if (isset($out['sharded']) && ! $out['sharded']) { 356 @trigger_error('Specifying false for "out.sharded" is deprecated.', E_USER_DEPRECATED); 357 } 358 } 359 360 /** 361 * Create the mapReduce command. 362 * 363 * @param Server $server 364 * @return Command 365 */ 366 private function createCommand(Server $server) 367 { 368 $cmd = [ 369 'mapReduce' => $this->collectionName, 370 'map' => $this->map, 371 'reduce' => $this->reduce, 372 'out' => $this->out, 373 ]; 374 375 foreach (['finalize', 'jsMode', 'limit', 'maxTimeMS', 'verbose'] as $option) { 376 if (isset($this->options[$option])) { 377 $cmd[$option] = $this->options[$option]; 378 } 379 } 380 381 foreach (['collation', 'query', 'scope', 'sort'] as $option) { 382 if (isset($this->options[$option])) { 383 $cmd[$option] = (object) $this->options[$option]; 384 } 385 } 386 387 if (! empty($this->options['bypassDocumentValidation']) && 388 server_supports_feature($server, self::$wireVersionForDocumentLevelValidation) 389 ) { 390 $cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation']; 391 } 392 393 return new Command($cmd); 394 } 395 396 /** 397 * Creates a callable for MapReduceResult::getIterator(). 398 * 399 * @param stdClass $result 400 * @param Server $server 401 * @return callable 402 * @throws UnexpectedValueException if the command response was malformed 403 */ 404 private function createGetIteratorCallable(stdClass $result, Server $server) 405 { 406 // Inline results can be wrapped with an ArrayIterator 407 if (isset($result->results) && is_array($result->results)) { 408 $results = $result->results; 409 410 return function () use ($results) { 411 return new ArrayIterator($results); 412 }; 413 } 414 415 if (isset($result->result) && (is_string($result->result) || is_object($result->result))) { 416 $options = isset($this->options['typeMap']) ? ['typeMap' => $this->options['typeMap']] : []; 417 418 $find = is_string($result->result) 419 ? new Find($this->databaseName, $result->result, [], $options) 420 : new Find($result->result->db, $result->result->collection, [], $options); 421 422 return function () use ($find, $server) { 423 return $find->execute($server); 424 }; 425 } 426 427 throw new UnexpectedValueException('mapReduce command did not return inline results or an output collection'); 428 } 429 430 /** 431 * Create options for executing the command. 432 * 433 * @see http://php.net/manual/en/mongodb-driver-server.executereadcommand.php 434 * @see http://php.net/manual/en/mongodb-driver-server.executereadwritecommand.php 435 * @param boolean $hasOutputCollection 436 * @return array 437 */ 438 private function createOptions($hasOutputCollection) 439 { 440 $options = []; 441 442 if (isset($this->options['readConcern'])) { 443 $options['readConcern'] = $this->options['readConcern']; 444 } 445 446 if (! $hasOutputCollection && isset($this->options['readPreference'])) { 447 $options['readPreference'] = $this->options['readPreference']; 448 } 449 450 if (isset($this->options['session'])) { 451 $options['session'] = $this->options['session']; 452 } 453 454 if ($hasOutputCollection && isset($this->options['writeConcern'])) { 455 $options['writeConcern'] = $this->options['writeConcern']; 456 } 457 458 return $options; 459 } 460 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body