Differences Between: [Versions 310 and 311] [Versions 311 and 401] [Versions 39 and 311]
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 MongoDB\Driver\Cursor; 21 use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 22 use MongoDB\Driver\Query; 23 use MongoDB\Driver\ReadConcern; 24 use MongoDB\Driver\ReadPreference; 25 use MongoDB\Driver\Server; 26 use MongoDB\Driver\Session; 27 use MongoDB\Exception\InvalidArgumentException; 28 use MongoDB\Exception\UnsupportedException; 29 use function is_array; 30 use function is_bool; 31 use function is_integer; 32 use function is_object; 33 use function is_string; 34 use function MongoDB\server_supports_feature; 35 use function trigger_error; 36 use const E_USER_DEPRECATED; 37 38 /** 39 * Operation for the find command. 40 * 41 * @api 42 * @see \MongoDB\Collection::find() 43 * @see http://docs.mongodb.org/manual/tutorial/query-documents/ 44 * @see http://docs.mongodb.org/manual/reference/operator/query-modifier/ 45 */ 46 class Find implements Executable, Explainable 47 { 48 const NON_TAILABLE = 1; 49 const TAILABLE = 2; 50 const TAILABLE_AWAIT = 3; 51 52 /** @var integer */ 53 private static $wireVersionForCollation = 5; 54 55 /** @var integer */ 56 private static $wireVersionForReadConcern = 4; 57 58 /** @var integer */ 59 private static $wireVersionForAllowDiskUseServerSideError = 4; 60 61 /** @var string */ 62 private $databaseName; 63 64 /** @var string */ 65 private $collectionName; 66 67 /** @var array|object */ 68 private $filter; 69 70 /** @var array */ 71 private $options; 72 73 /** 74 * Constructs a find command. 75 * 76 * Supported options: 77 * 78 * * allowDiskUse (boolean): Enables writing to temporary files. When set 79 * to true, queries can write data to the _tmp sub-directory in the 80 * dbPath directory. The default is false. 81 * 82 * * allowPartialResults (boolean): Get partial results from a mongos if 83 * some shards are inaccessible (instead of throwing an error). 84 * 85 * * batchSize (integer): The number of documents to return per batch. 86 * 87 * * collation (document): Collation specification. 88 * 89 * This is not supported for server versions < 3.4 and will result in an 90 * exception at execution time if used. 91 * 92 * * comment (string): Attaches a comment to the query. If "$comment" also 93 * exists in the modifiers document, this option will take precedence. 94 * 95 * * cursorType (enum): Indicates the type of cursor to use. Must be either 96 * NON_TAILABLE, TAILABLE, or TAILABLE_AWAIT. The default is 97 * NON_TAILABLE. 98 * 99 * * hint (string|document): The index to use. Specify either the index 100 * name as a string or the index key pattern as a document. If specified, 101 * then the query system will only consider plans using the hinted index. 102 * 103 * * limit (integer): The maximum number of documents to return. 104 * 105 * * max (document): The exclusive upper bound for a specific index. 106 * 107 * * maxAwaitTimeMS (integer): The maxium amount of time for the server to wait 108 * on new documents to satisfy a query, if cursorType is TAILABLE_AWAIT. 109 * 110 * * maxScan (integer): Maximum number of documents or index keys to scan 111 * when executing the query. 112 * 113 * This option has been deprecated since version 1.4. 114 * 115 * * maxTimeMS (integer): The maximum amount of time to allow the query to 116 * run. If "$maxTimeMS" also exists in the modifiers document, this 117 * option will take precedence. 118 * 119 * * min (document): The inclusive upper bound for a specific index. 120 * 121 * * modifiers (document): Meta operators that modify the output or 122 * behavior of a query. Use of these operators is deprecated in favor of 123 * named options. 124 * 125 * * noCursorTimeout (boolean): The server normally times out idle cursors 126 * after an inactivity period (10 minutes) to prevent excess memory use. 127 * Set this option to prevent that. 128 * 129 * * oplogReplay (boolean): Internal replication use only. The driver 130 * should not set this. This option is deprecated as of MongoDB 4.4. 131 * 132 * * projection (document): Limits the fields to return for the matching 133 * document. 134 * 135 * * readConcern (MongoDB\Driver\ReadConcern): Read concern. 136 * 137 * This is not supported for server versions < 3.2 and will result in an 138 * exception at execution time if used. 139 * 140 * * readPreference (MongoDB\Driver\ReadPreference): Read preference. 141 * 142 * * returnKey (boolean): If true, returns only the index keys in the 143 * resulting documents. 144 * 145 * * session (MongoDB\Driver\Session): Client session. 146 * 147 * Sessions are not supported for server versions < 3.6. 148 * 149 * * showRecordId (boolean): Determines whether to return the record 150 * identifier for each document. If true, adds a field $recordId to the 151 * returned documents. 152 * 153 * * skip (integer): The number of documents to skip before returning. 154 * 155 * * snapshot (boolean): Prevents the cursor from returning a document more 156 * than once because of an intervening write operation. 157 * 158 * This options has been deprecated since version 1.4. 159 * 160 * * sort (document): The order in which to return matching documents. If 161 * "$orderby" also exists in the modifiers document, this option will 162 * take precedence. 163 * 164 * * typeMap (array): Type map for BSON deserialization. This will be 165 * applied to the returned Cursor (it is not sent to the server). 166 * 167 * @param string $databaseName Database name 168 * @param string $collectionName Collection name 169 * @param array|object $filter Query by which to filter documents 170 * @param array $options Command options 171 * @throws InvalidArgumentException for parameter/option parsing errors 172 */ 173 public function __construct($databaseName, $collectionName, $filter, array $options = []) 174 { 175 if (! is_array($filter) && ! is_object($filter)) { 176 throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object'); 177 } 178 179 if (isset($options['allowDiskUse']) && ! is_bool($options['allowDiskUse'])) { 180 throw InvalidArgumentException::invalidType('"allowDiskUse" option', $options['allowDiskUse'], 'boolean'); 181 } 182 183 if (isset($options['allowPartialResults']) && ! is_bool($options['allowPartialResults'])) { 184 throw InvalidArgumentException::invalidType('"allowPartialResults" option', $options['allowPartialResults'], 'boolean'); 185 } 186 187 if (isset($options['batchSize']) && ! is_integer($options['batchSize'])) { 188 throw InvalidArgumentException::invalidType('"batchSize" option', $options['batchSize'], 'integer'); 189 } 190 191 if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) { 192 throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object'); 193 } 194 195 if (isset($options['comment']) && ! is_string($options['comment'])) { 196 throw InvalidArgumentException::invalidType('"comment" option', $options['comment'], 'comment'); 197 } 198 199 if (isset($options['cursorType'])) { 200 if (! is_integer($options['cursorType'])) { 201 throw InvalidArgumentException::invalidType('"cursorType" option', $options['cursorType'], 'integer'); 202 } 203 204 if ($options['cursorType'] !== self::NON_TAILABLE && 205 $options['cursorType'] !== self::TAILABLE && 206 $options['cursorType'] !== self::TAILABLE_AWAIT) { 207 throw new InvalidArgumentException('Invalid value for "cursorType" option: ' . $options['cursorType']); 208 } 209 } 210 211 if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) { 212 throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], 'string or array or object'); 213 } 214 215 if (isset($options['limit']) && ! is_integer($options['limit'])) { 216 throw InvalidArgumentException::invalidType('"limit" option', $options['limit'], 'integer'); 217 } 218 219 if (isset($options['max']) && ! is_array($options['max']) && ! is_object($options['max'])) { 220 throw InvalidArgumentException::invalidType('"max" option', $options['max'], 'array or object'); 221 } 222 223 if (isset($options['maxAwaitTimeMS']) && ! is_integer($options['maxAwaitTimeMS'])) { 224 throw InvalidArgumentException::invalidType('"maxAwaitTimeMS" option', $options['maxAwaitTimeMS'], 'integer'); 225 } 226 227 if (isset($options['maxScan']) && ! is_integer($options['maxScan'])) { 228 throw InvalidArgumentException::invalidType('"maxScan" option', $options['maxScan'], 'integer'); 229 } 230 231 if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { 232 throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); 233 } 234 235 if (isset($options['min']) && ! is_array($options['min']) && ! is_object($options['min'])) { 236 throw InvalidArgumentException::invalidType('"min" option', $options['min'], 'array or object'); 237 } 238 239 if (isset($options['modifiers']) && ! is_array($options['modifiers']) && ! is_object($options['modifiers'])) { 240 throw InvalidArgumentException::invalidType('"modifiers" option', $options['modifiers'], 'array or object'); 241 } 242 243 if (isset($options['noCursorTimeout']) && ! is_bool($options['noCursorTimeout'])) { 244 throw InvalidArgumentException::invalidType('"noCursorTimeout" option', $options['noCursorTimeout'], 'boolean'); 245 } 246 247 if (isset($options['oplogReplay']) && ! is_bool($options['oplogReplay'])) { 248 throw InvalidArgumentException::invalidType('"oplogReplay" option', $options['oplogReplay'], 'boolean'); 249 } 250 251 if (isset($options['projection']) && ! is_array($options['projection']) && ! is_object($options['projection'])) { 252 throw InvalidArgumentException::invalidType('"projection" option', $options['projection'], 'array or object'); 253 } 254 255 if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { 256 throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); 257 } 258 259 if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { 260 throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); 261 } 262 263 if (isset($options['returnKey']) && ! is_bool($options['returnKey'])) { 264 throw InvalidArgumentException::invalidType('"returnKey" option', $options['returnKey'], 'boolean'); 265 } 266 267 if (isset($options['session']) && ! $options['session'] instanceof Session) { 268 throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); 269 } 270 271 if (isset($options['showRecordId']) && ! is_bool($options['showRecordId'])) { 272 throw InvalidArgumentException::invalidType('"showRecordId" option', $options['showRecordId'], 'boolean'); 273 } 274 275 if (isset($options['skip']) && ! is_integer($options['skip'])) { 276 throw InvalidArgumentException::invalidType('"skip" option', $options['skip'], 'integer'); 277 } 278 279 if (isset($options['snapshot']) && ! is_bool($options['snapshot'])) { 280 throw InvalidArgumentException::invalidType('"snapshot" option', $options['snapshot'], 'boolean'); 281 } 282 283 if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) { 284 throw InvalidArgumentException::invalidType('"sort" option', $options['sort'], 'array or object'); 285 } 286 287 if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { 288 throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); 289 } 290 291 if (isset($options['readConcern']) && $options['readConcern']->isDefault()) { 292 unset($options['readConcern']); 293 } 294 295 if (isset($options['snapshot'])) { 296 trigger_error('The "snapshot" option is deprecated and will be removed in a future release', E_USER_DEPRECATED); 297 } 298 299 if (isset($options['maxScan'])) { 300 trigger_error('The "maxScan" option is deprecated and will be removed in a future release', E_USER_DEPRECATED); 301 } 302 303 $this->databaseName = (string) $databaseName; 304 $this->collectionName = (string) $collectionName; 305 $this->filter = $filter; 306 $this->options = $options; 307 } 308 309 /** 310 * Execute the operation. 311 * 312 * @see Executable::execute() 313 * @param Server $server 314 * @return Cursor 315 * @throws UnsupportedException if collation or read concern is used and unsupported 316 * @throws DriverRuntimeException for other driver errors (e.g. connection errors) 317 */ 318 public function execute(Server $server) 319 { 320 if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) { 321 throw UnsupportedException::collationNotSupported(); 322 } 323 324 if (isset($this->options['readConcern']) && ! server_supports_feature($server, self::$wireVersionForReadConcern)) { 325 throw UnsupportedException::readConcernNotSupported(); 326 } 327 328 if (isset($this->options['allowDiskUse']) && ! server_supports_feature($server, self::$wireVersionForAllowDiskUseServerSideError)) { 329 throw UnsupportedException::allowDiskUseNotSupported(); 330 } 331 332 $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); 333 if ($inTransaction && isset($this->options['readConcern'])) { 334 throw UnsupportedException::readConcernNotSupportedInTransaction(); 335 } 336 337 $cursor = $server->executeQuery($this->databaseName . '.' . $this->collectionName, new Query($this->filter, $this->createQueryOptions()), $this->createExecuteOptions()); 338 339 if (isset($this->options['typeMap'])) { 340 $cursor->setTypeMap($this->options['typeMap']); 341 } 342 343 return $cursor; 344 } 345 346 public function getCommandDocument(Server $server) 347 { 348 return $this->createCommandDocument(); 349 } 350 351 /** 352 * Construct a command document for Find 353 */ 354 private function createCommandDocument() 355 { 356 $cmd = ['find' => $this->collectionName, 'filter' => (object) $this->filter]; 357 358 $options = $this->createQueryOptions(); 359 360 if (empty($options)) { 361 return $cmd; 362 } 363 364 // maxAwaitTimeMS is a Query level option so should not be considered here 365 unset($options['maxAwaitTimeMS']); 366 367 $modifierFallback = [ 368 ['allowPartialResults', 'partial'], 369 ['comment', '$comment'], 370 ['hint', '$hint'], 371 ['maxScan', '$maxScan'], 372 ['max', '$max'], 373 ['maxTimeMS', '$maxTimeMS'], 374 ['min', '$min'], 375 ['returnKey', '$returnKey'], 376 ['showRecordId', '$showDiskLoc'], 377 ['sort', '$orderby'], 378 ['snapshot', '$snapshot'], 379 ]; 380 381 foreach ($modifierFallback as $modifier) { 382 if (! isset($options[$modifier[0]]) && isset($options['modifiers'][$modifier[1]])) { 383 $options[$modifier[0]] = $options['modifiers'][$modifier[1]]; 384 } 385 } 386 unset($options['modifiers']); 387 388 return $cmd + $options; 389 } 390 391 /** 392 * Create options for executing the command. 393 * 394 * @see http://php.net/manual/en/mongodb-driver-server.executequery.php 395 * @return array 396 */ 397 private function createExecuteOptions() 398 { 399 $options = []; 400 401 if (isset($this->options['readPreference'])) { 402 $options['readPreference'] = $this->options['readPreference']; 403 } 404 405 if (isset($this->options['session'])) { 406 $options['session'] = $this->options['session']; 407 } 408 409 return $options; 410 } 411 412 /** 413 * Create options for the find query. 414 * 415 * Note that these are separate from the options for executing the command, 416 * which are created in createExecuteOptions(). 417 * 418 * @return array 419 */ 420 private function createQueryOptions() 421 { 422 $options = []; 423 424 if (isset($this->options['cursorType'])) { 425 if ($this->options['cursorType'] === self::TAILABLE) { 426 $options['tailable'] = true; 427 } 428 if ($this->options['cursorType'] === self::TAILABLE_AWAIT) { 429 $options['tailable'] = true; 430 $options['awaitData'] = true; 431 } 432 } 433 434 foreach (['allowDiskUse', 'allowPartialResults', 'batchSize', 'comment', 'hint', 'limit', 'maxAwaitTimeMS', 'maxScan', 'maxTimeMS', 'noCursorTimeout', 'oplogReplay', 'projection', 'readConcern', 'returnKey', 'showRecordId', 'skip', 'snapshot', 'sort'] as $option) { 435 if (isset($this->options[$option])) { 436 $options[$option] = $this->options[$option]; 437 } 438 } 439 440 foreach (['collation', 'max', 'min'] as $option) { 441 if (isset($this->options[$option])) { 442 $options[$option] = (object) $this->options[$option]; 443 } 444 } 445 446 $modifiers = empty($this->options['modifiers']) ? [] : (array) $this->options['modifiers']; 447 448 if (! empty($modifiers)) { 449 $options['modifiers'] = $modifiers; 450 } 451 452 return $options; 453 } 454 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body