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 MongoDB\Driver\Command; 21 use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 22 use MongoDB\Driver\Server; 23 use MongoDB\Driver\Session; 24 use MongoDB\Driver\WriteConcern; 25 use MongoDB\Exception\InvalidArgumentException; 26 use MongoDB\Exception\UnexpectedValueException; 27 use MongoDB\Exception\UnsupportedException; 28 use function current; 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\create_field_path_type_map; 35 use function MongoDB\is_pipeline; 36 use function MongoDB\server_supports_feature; 37 38 /** 39 * Operation for the findAndModify command. 40 * 41 * This class is used internally by the FindOneAndDelete, FindOneAndReplace, and 42 * FindOneAndUpdate operation classes. 43 * 44 * @internal 45 * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ 46 */ 47 class FindAndModify implements Executable, Explainable 48 { 49 /** @var integer */ 50 private static $wireVersionForArrayFilters = 6; 51 52 /** @var integer */ 53 private static $wireVersionForCollation = 5; 54 55 /** @var integer */ 56 private static $wireVersionForDocumentLevelValidation = 4; 57 58 /** @var integer */ 59 private static $wireVersionForHint = 9; 60 61 /** @var integer */ 62 private static $wireVersionForHintServerSideError = 8; 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 array */ 74 private $options; 75 76 /** 77 * Constructs a findAndModify command. 78 * 79 * Supported options: 80 * 81 * * arrayFilters (document array): A set of filters specifying to which 82 * array elements an update should apply. 83 * 84 * This is not supported for server versions < 3.6 and will result in an 85 * exception at execution time if used. 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 * * bypassDocumentValidation (boolean): If true, allows the write to 93 * circumvent document level validation. 94 * 95 * For servers < 3.2, this option is ignored as document level validation 96 * is not available. 97 * 98 * * fields (document): Limits the fields to return for the matching 99 * document. 100 * 101 * * hint (string|document): The index to use. Specify either the index 102 * name as a string or the index key pattern as a document. If specified, 103 * then the query system will only consider plans using the hinted index. 104 * 105 * This is only supported on server versions >= 4.4. Using this option in 106 * other contexts will result in an exception at execution time. 107 * 108 * * maxTimeMS (integer): The maximum amount of time to allow the query to 109 * run. 110 * 111 * * new (boolean): When true, returns the modified document rather than 112 * the original. This option is ignored for remove operations. The 113 * The default is false. 114 * 115 * * query (document): Query by which to filter documents. 116 * 117 * * remove (boolean): When true, removes the matched document. This option 118 * cannot be true if the update option is set. The default is false. 119 * 120 * * session (MongoDB\Driver\Session): Client session. 121 * 122 * Sessions are not supported for server versions < 3.6. 123 * 124 * * sort (document): Determines which document the operation modifies if 125 * the query selects multiple documents. 126 * 127 * * typeMap (array): Type map for BSON deserialization. 128 * 129 * * update (document): Update or replacement to apply to the matched 130 * document. This option cannot be set if the remove option is true. 131 * 132 * * upsert (boolean): When true, a new document is created if no document 133 * matches the query. This option is ignored for remove operations. The 134 * default is false. 135 * 136 * * writeConcern (MongoDB\Driver\WriteConcern): Write concern. 137 * 138 * This is not supported for server versions < 3.2 and will result in an 139 * exception at execution time if used. 140 * 141 * @param string $databaseName Database name 142 * @param string $collectionName Collection name 143 * @param array $options Command options 144 * @throws InvalidArgumentException for parameter/option parsing errors 145 */ 146 public function __construct($databaseName, $collectionName, array $options) 147 { 148 $options += [ 149 'new' => false, 150 'remove' => false, 151 'upsert' => false, 152 ]; 153 154 if (isset($options['arrayFilters']) && ! is_array($options['arrayFilters'])) { 155 throw InvalidArgumentException::invalidType('"arrayFilters" option', $options['arrayFilters'], 'array'); 156 } 157 158 if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) { 159 throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean'); 160 } 161 162 if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) { 163 throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object'); 164 } 165 166 if (isset($options['fields']) && ! is_array($options['fields']) && ! is_object($options['fields'])) { 167 throw InvalidArgumentException::invalidType('"fields" option', $options['fields'], 'array or object'); 168 } 169 170 if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) { 171 throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], ['string', 'array', 'object']); 172 } 173 174 if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { 175 throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); 176 } 177 178 if (! is_bool($options['new'])) { 179 throw InvalidArgumentException::invalidType('"new" option', $options['new'], 'boolean'); 180 } 181 182 if (isset($options['query']) && ! is_array($options['query']) && ! is_object($options['query'])) { 183 throw InvalidArgumentException::invalidType('"query" option', $options['query'], 'array or object'); 184 } 185 186 if (! is_bool($options['remove'])) { 187 throw InvalidArgumentException::invalidType('"remove" option', $options['remove'], 'boolean'); 188 } 189 190 if (isset($options['session']) && ! $options['session'] instanceof Session) { 191 throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); 192 } 193 194 if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) { 195 throw InvalidArgumentException::invalidType('"sort" option', $options['sort'], 'array or object'); 196 } 197 198 if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { 199 throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); 200 } 201 202 if (isset($options['update']) && ! is_array($options['update']) && ! is_object($options['update'])) { 203 throw InvalidArgumentException::invalidType('"update" option', $options['update'], 'array or object'); 204 } 205 206 if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { 207 throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); 208 } 209 210 if (! is_bool($options['upsert'])) { 211 throw InvalidArgumentException::invalidType('"upsert" option', $options['upsert'], 'boolean'); 212 } 213 214 if (! (isset($options['update']) xor $options['remove'])) { 215 throw new InvalidArgumentException('The "remove" option must be true or an "update" document must be specified, but not both'); 216 } 217 218 if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { 219 unset($options['writeConcern']); 220 } 221 222 $this->databaseName = (string) $databaseName; 223 $this->collectionName = (string) $collectionName; 224 $this->options = $options; 225 } 226 227 /** 228 * Execute the operation. 229 * 230 * @see Executable::execute() 231 * @param Server $server 232 * @return array|object|null 233 * @throws UnexpectedValueException if the command response was malformed 234 * @throws UnsupportedException if array filters, collation, or write concern is used and unsupported 235 * @throws DriverRuntimeException for other driver errors (e.g. connection errors) 236 */ 237 public function execute(Server $server) 238 { 239 if (isset($this->options['arrayFilters']) && ! server_supports_feature($server, self::$wireVersionForArrayFilters)) { 240 throw UnsupportedException::arrayFiltersNotSupported(); 241 } 242 243 if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) { 244 throw UnsupportedException::collationNotSupported(); 245 } 246 247 /* Server versions >= 4.1.10 raise errors for unknown findAndModify 248 * options (SERVER-40005), but the CRUD spec requires client-side errors 249 * for server versions < 4.2. For later versions, we'll rely on the 250 * server to either utilize the option or report its own error. */ 251 if (isset($this->options['hint']) && ! $this->isHintSupported($server)) { 252 throw UnsupportedException::hintNotSupported(); 253 } 254 255 if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) { 256 throw UnsupportedException::writeConcernNotSupported(); 257 } 258 259 $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); 260 if ($inTransaction && isset($this->options['writeConcern'])) { 261 throw UnsupportedException::writeConcernNotSupportedInTransaction(); 262 } 263 264 $cursor = $server->executeWriteCommand($this->databaseName, new Command($this->createCommandDocument($server)), $this->createOptions()); 265 266 if (isset($this->options['typeMap'])) { 267 $cursor->setTypeMap(create_field_path_type_map($this->options['typeMap'], 'value')); 268 } 269 270 $result = current($cursor->toArray()); 271 272 return $result->value ?? null; 273 } 274 275 public function getCommandDocument(Server $server) 276 { 277 return $this->createCommandDocument($server); 278 } 279 280 /** 281 * Create the findAndModify command document. 282 * 283 * @param Server $server 284 * @return array 285 */ 286 private function createCommandDocument(Server $server) 287 { 288 $cmd = ['findAndModify' => $this->collectionName]; 289 290 if ($this->options['remove']) { 291 $cmd['remove'] = true; 292 } else { 293 $cmd['new'] = $this->options['new']; 294 $cmd['upsert'] = $this->options['upsert']; 295 } 296 297 foreach (['collation', 'fields', 'query', 'sort'] as $option) { 298 if (isset($this->options[$option])) { 299 $cmd[$option] = (object) $this->options[$option]; 300 } 301 } 302 303 if (isset($this->options['update'])) { 304 $cmd['update'] = is_pipeline($this->options['update']) 305 ? $this->options['update'] 306 : (object) $this->options['update']; 307 } 308 309 foreach (['arrayFilters', 'hint', 'maxTimeMS'] as $option) { 310 if (isset($this->options[$option])) { 311 $cmd[$option] = $this->options[$option]; 312 } 313 } 314 315 if (! empty($this->options['bypassDocumentValidation']) && 316 server_supports_feature($server, self::$wireVersionForDocumentLevelValidation) 317 ) { 318 $cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation']; 319 } 320 321 return $cmd; 322 } 323 324 /** 325 * Create options for executing the command. 326 * 327 * @see http://php.net/manual/en/mongodb-driver-server.executewritecommand.php 328 * @return array 329 */ 330 private function createOptions() 331 { 332 $options = []; 333 334 if (isset($this->options['session'])) { 335 $options['session'] = $this->options['session']; 336 } 337 338 if (isset($this->options['writeConcern'])) { 339 $options['writeConcern'] = $this->options['writeConcern']; 340 } 341 342 return $options; 343 } 344 345 private function isAcknowledgedWriteConcern() : bool 346 { 347 if (! isset($this->options['writeConcern'])) { 348 return true; 349 } 350 351 return $this->options['writeConcern']->getW() > 1 || $this->options['writeConcern']->getJournal(); 352 } 353 354 private function isHintSupported(Server $server) : bool 355 { 356 $requiredWireVersion = $this->isAcknowledgedWriteConcern() ? self::$wireVersionForHintServerSideError : self::$wireVersionForHint; 357 358 return server_supports_feature($server, $requiredWireVersion); 359 } 360 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body