Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [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\ReadConcern;
  23  use MongoDB\Driver\ReadPreference;
  24  use MongoDB\Driver\Server;
  25  use MongoDB\Driver\Session;
  26  use MongoDB\Exception\InvalidArgumentException;
  27  use MongoDB\Exception\UnexpectedValueException;
  28  use MongoDB\Exception\UnsupportedException;
  29  use function current;
  30  use function is_array;
  31  use function is_float;
  32  use function is_integer;
  33  use function is_object;
  34  use function is_string;
  35  use function MongoDB\server_supports_feature;
  36  
  37  /**
  38   * Operation for the count command.
  39   *
  40   * @api
  41   * @see \MongoDB\Collection::count()
  42   * @see http://docs.mongodb.org/manual/reference/command/count/
  43   */
  44  class Count implements Executable, Explainable
  45  {
  46      /** @var integer */
  47      private static $wireVersionForCollation = 5;
  48  
  49      /** @var integer */
  50      private static $wireVersionForReadConcern = 4;
  51  
  52      /** @var string */
  53      private $databaseName;
  54  
  55      /** @var string */
  56      private $collectionName;
  57  
  58      /** @var array|object */
  59      private $filter;
  60  
  61      /** @var array */
  62      private $options;
  63  
  64      /**
  65       * Constructs a count command.
  66       *
  67       * Supported options:
  68       *
  69       *  * collation (document): Collation specification.
  70       *
  71       *    This is not supported for server versions < 3.4 and will result in an
  72       *    exception at execution time if used.
  73       *
  74       *  * hint (string|document): The index to use. Specify either the index
  75       *    name as a string or the index key pattern as a document. If specified,
  76       *    then the query system will only consider plans using the hinted index.
  77       *
  78       *  * limit (integer): The maximum number of documents to count.
  79       *
  80       *  * maxTimeMS (integer): The maximum amount of time to allow the query to
  81       *    run.
  82       *
  83       *  * readConcern (MongoDB\Driver\ReadConcern): Read concern.
  84       *
  85       *    This is not supported for server versions < 3.2 and will result in an
  86       *    exception at execution time if used.
  87       *
  88       *  * readPreference (MongoDB\Driver\ReadPreference): Read preference.
  89       *
  90       *  * session (MongoDB\Driver\Session): Client session.
  91       *
  92       *    Sessions are not supported for server versions < 3.6.
  93       *
  94       *  * skip (integer): The number of documents to skip before returning the
  95       *    documents.
  96       *
  97       * @param string       $databaseName   Database name
  98       * @param string       $collectionName Collection name
  99       * @param array|object $filter         Query by which to filter documents
 100       * @param array        $options        Command options
 101       * @throws InvalidArgumentException for parameter/option parsing errors
 102       */
 103      public function __construct($databaseName, $collectionName, $filter = [], array $options = [])
 104      {
 105          if (! is_array($filter) && ! is_object($filter)) {
 106              throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
 107          }
 108  
 109          if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
 110              throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
 111          }
 112  
 113          if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) {
 114              throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], 'string or array or object');
 115          }
 116  
 117          if (isset($options['limit']) && ! is_integer($options['limit'])) {
 118              throw InvalidArgumentException::invalidType('"limit" option', $options['limit'], 'integer');
 119          }
 120  
 121          if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
 122              throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
 123          }
 124  
 125          if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
 126              throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
 127          }
 128  
 129          if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
 130              throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
 131          }
 132  
 133          if (isset($options['session']) && ! $options['session'] instanceof Session) {
 134              throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
 135          }
 136  
 137          if (isset($options['skip']) && ! is_integer($options['skip'])) {
 138              throw InvalidArgumentException::invalidType('"skip" option', $options['skip'], 'integer');
 139          }
 140  
 141          if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
 142              unset($options['readConcern']);
 143          }
 144  
 145          $this->databaseName = (string) $databaseName;
 146          $this->collectionName = (string) $collectionName;
 147          $this->filter = $filter;
 148          $this->options = $options;
 149      }
 150  
 151      /**
 152       * Execute the operation.
 153       *
 154       * @see Executable::execute()
 155       * @param Server $server
 156       * @return integer
 157       * @throws UnexpectedValueException if the command response was malformed
 158       * @throws UnsupportedException if collation or read concern is used and unsupported
 159       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 160       */
 161      public function execute(Server $server)
 162      {
 163          if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) {
 164              throw UnsupportedException::collationNotSupported();
 165          }
 166  
 167          if (isset($this->options['readConcern']) && ! server_supports_feature($server, self::$wireVersionForReadConcern)) {
 168              throw UnsupportedException::readConcernNotSupported();
 169          }
 170  
 171          $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
 172          if ($inTransaction && isset($this->options['readConcern'])) {
 173              throw UnsupportedException::readConcernNotSupportedInTransaction();
 174          }
 175  
 176          $cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
 177          $result = current($cursor->toArray());
 178  
 179          // Older server versions may return a float
 180          if (! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) {
 181              throw new UnexpectedValueException('count command did not return a numeric "n" value');
 182          }
 183  
 184          return (integer) $result->n;
 185      }
 186  
 187      public function getCommandDocument(Server $server)
 188      {
 189          return $this->createCommandDocument();
 190      }
 191  
 192      /**
 193       * Create the count command document.
 194       *
 195       * @return array
 196       */
 197      private function createCommandDocument()
 198      {
 199          $cmd = ['count' => $this->collectionName];
 200  
 201          if (! empty($this->filter)) {
 202              $cmd['query'] = (object) $this->filter;
 203          }
 204  
 205          if (isset($this->options['collation'])) {
 206              $cmd['collation'] = (object) $this->options['collation'];
 207          }
 208  
 209          if (isset($this->options['hint'])) {
 210              $cmd['hint'] = is_array($this->options['hint']) ? (object) $this->options['hint'] : $this->options['hint'];
 211          }
 212  
 213          foreach (['limit', 'maxTimeMS', 'skip'] as $option) {
 214              if (isset($this->options[$option])) {
 215                  $cmd[$option] = $this->options[$option];
 216              }
 217          }
 218  
 219          return $cmd;
 220      }
 221  
 222      /**
 223       * Create options for executing the command.
 224       *
 225       * @see http://php.net/manual/en/mongodb-driver-server.executereadcommand.php
 226       * @return array
 227       */
 228      private function createOptions()
 229      {
 230          $options = [];
 231  
 232          if (isset($this->options['readConcern'])) {
 233              $options['readConcern'] = $this->options['readConcern'];
 234          }
 235  
 236          if (isset($this->options['readPreference'])) {
 237              $options['readPreference'] = $this->options['readPreference'];
 238          }
 239  
 240          if (isset($this->options['session'])) {
 241              $options['session'] = $this->options['session'];
 242          }
 243  
 244          return $options;
 245      }
 246  }