├── .gitignore ├── src ├── Schema │ └── Builder.php ├── Query │ ├── Processors │ │ └── MongoProcessor.php │ ├── Builder.php │ └── Grammars │ │ └── MongoGrammar.php ├── MongoServiceProvider.php ├── Connectors │ └── MongoConnector.php ├── MongoConnectionAliasMethods.php └── Connection.php ├── Makefile ├── docker-compose.yml ├── tests ├── ConnectionTest.php ├── TestCase.php └── Query │ └── BuilderTest.php ├── Dockerfile ├── .php_cs ├── .travis.yml ├── phpunit.xml.dist ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | .php_cs.cache 3 | clover.xml 4 | composer.lock 5 | -------------------------------------------------------------------------------- /src/Schema/Builder.php: -------------------------------------------------------------------------------- 1 | connection = $this->app->get('db')->connection('mongodb'); 19 | } 20 | 21 | public function testConnectionInstance() 22 | { 23 | $this->assertInstanceOf(Connection::class, $this->connection); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PHP_VERSION=7.2 2 | ARG COMPOSER_VERSION=1.8 3 | 4 | FROM composer:${COMPOSER_VERSION} 5 | FROM php:${PHP_VERSION}-cli 6 | 7 | RUN set -eux; \ 8 | if [ $(php -r "echo PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION;") = "7.3" ]; \ 9 | then \ 10 | pecl install xdebug-beta; \ 11 | else \ 12 | pecl install xdebug; \ 13 | fi && \ 14 | docker-php-ext-enable xdebug 15 | 16 | RUN apt-get update && \ 17 | apt-get install -y git zip unzip && \ 18 | pecl install mongodb && docker-php-ext-enable mongodb 19 | 20 | COPY --from=composer /usr/bin/composer /usr/local/bin/composer 21 | 22 | WORKDIR /code 23 | -------------------------------------------------------------------------------- /src/Query/Processors/MongoProcessor.php: -------------------------------------------------------------------------------- 1 | getConnection()->insert($sql, $values); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | in(['src', 'tests']); 8 | 9 | return Config::create() 10 | ->setRules([ 11 | '@Symfony' => true, 12 | 'ordered_imports' => true, 13 | 'phpdoc_align' => false, 14 | 'phpdoc_to_comment' => false, 15 | 'phpdoc_inline_tag' => false, 16 | 'yoda_style' => false, 17 | 'blank_line_before_statement' => false, 18 | 'phpdoc_separation' => false, 19 | 'concat_space' => [ 20 | 'spacing' => 'one', 21 | ], 22 | 'array_syntax' => [ 23 | 'syntax' => 'short', 24 | ], 25 | ]) 26 | ->setFinder($finder); 27 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | set('database.default', 'mongodb'); 19 | $app['config']->set('database.connections.mongodb', [ 20 | 'name' => 'mongodb', 21 | 'driver' => 'mongodb', 22 | 'host' => 'mongodb', 23 | 'database' => 'unittest', 24 | ]); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: minimal 2 | 3 | matrix: 4 | include: 5 | - name: "7.1" 6 | env: PHP_VERSION=7.1 7 | - name: "7.2" 8 | env: PHP_VERSION=7.2 9 | - name: "7.3" 10 | env: PHP_VERSION=7.3 11 | 12 | services: 13 | - docker 14 | 15 | cache: 16 | directories: 17 | - $HOME/.composer/cache 18 | 19 | install: 20 | - docker version 21 | - sudo pip install docker-compose 22 | - docker-compose version 23 | - docker-compose build --build-arg PHP_VERSION=${PHP_VERSION} 24 | 25 | script: 26 | - docker-compose run --rm --no-deps tests composer install --no-interaction 27 | - docker-compose run --rm tests ./vendor/bin/phpunit --coverage-clover ./clover.xml 28 | 29 | after_script: 30 | - docker-compose run --rm --no-deps tests ./vendor/bin/php-coveralls -v 31 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | tests/ 16 | 17 | 18 | 19 | 20 | ./src 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jenssegers/mongodb-core", 3 | "description": "A MongoDB based Query builder for Laravel", 4 | "keywords": ["laravel","mongodb","mongo","database","model","moloquent"], 5 | "homepage": "https://github.com/jenssegers/laravel-mongodb-core", 6 | "authors": [ 7 | { 8 | "name": "Jens Segers", 9 | "homepage": "https://jenssegers.com" 10 | } 11 | ], 12 | "license" : "MIT", 13 | "require": { 14 | "ext-mongodb": "*", 15 | "illuminate/database": "^5.6", 16 | "mongodb/mongodb": "^1.0" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "^6.0|^7.0", 20 | "orchestra/testbench": "^3.7", 21 | "php-coveralls/php-coveralls": "^2.0" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Jenssegers\\Mongodb\\": "src/" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "psr-4": { 30 | "Jenssegers\\Mongodb\\Tests\\": "tests/" 31 | } 32 | }, 33 | "suggest": { 34 | "jenssegers/mongodb": "Add Eloquent support to laravel-mongodb", 35 | "jenssegers/mongodb-session": "Add MongoDB session support to laravel-mongodb", 36 | "jenssegers/mongodb-sentry": "Add Sentry support to laravel-mongodb" 37 | }, 38 | "extra": { 39 | "laravel": { 40 | "providers": [ 41 | "Jenssegers\\Mongodb\\MongodbServiceProvider" 42 | ] 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/MongoServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerConnectionServices(); 19 | } 20 | 21 | /** 22 | * Register the primary database bindings. 23 | */ 24 | protected function registerConnectionServices() 25 | { 26 | // Add database connector to connection factory. 27 | $this->app->singleton('db.connector.mongodb', function ($app) { 28 | return new MongoConnector(); 29 | }); 30 | 31 | // Add database driver. 32 | $this->app->resolving('db', function (DatabaseManager $db) { 33 | // Register connection resolver. 34 | Connection::resolverFor('mongodb', function ($connection, $database, $prefix, $config) { 35 | return new MongoConnection($connection, $database, $prefix, $config); 36 | }); 37 | 38 | $db->extend('mongodb', function ($config, $name) { 39 | return $this->app->get('db.factory')->make($config, $name); 40 | }); 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Connectors/MongoConnector.php: -------------------------------------------------------------------------------- 1 | getDsn($config), $options, $driverOptions); 37 | } 38 | 39 | /** 40 | * Create a DSN string from a configuration. 41 | * 42 | * @param array $config 43 | * @return string 44 | */ 45 | protected function getDsn(array $config) 46 | { 47 | // Check if the config has a DSN string. 48 | if (isset($config['dsn']) && !empty($config['dsn'])) { 49 | return $config['dsn']; 50 | } 51 | 52 | // Treat host option as array of hosts. 53 | $hosts = is_array($config['host']) ? $config['host'] : [$config['host']]; 54 | 55 | foreach ($hosts as &$host) { 56 | // Check if we need to add a port to the host. 57 | if (!empty($config['port']) && strpos($host, ':') === false) { 58 | $host = $host . ':' . $config['port']; 59 | } 60 | } 61 | 62 | // Check if we want to authenticate against a specific database. 63 | $authDatabase = isset($config['options']) && !empty($config['options']['database']) ? $config['options']['database'] : null; 64 | 65 | return 'mongodb://' . implode(',', $hosts) . ($authDatabase ? '/' . $authDatabase : ''); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Laravel MongoDB Core 2 | ==================== 3 | 4 | The MongoDB Core package that powers [laravel-mongodb](https://github.com/jenssegers/laravel-mongodb). 5 | 6 | This package provides core functionality to your Laravel application to connect to a Mongo database. It provides a Mongo database connector and a query builder. If you want to have MongoDB support for your Eloquent models, check out [laravel-mongodb](https://github.com/jenssegers/laravel-mongodb). 7 | 8 | Contributing 9 | ------------ 10 | 11 | This package is still under heavy development. I'm starting a complete rewrite of [laravel-mongodb](https://github.com/jenssegers/laravel-mongodb), starting with splitting of the core functionality into a separate package. 12 | 13 | Laravel has changed a lot since the original code was written. I have found a much more elegant way of extending the Laravel query builder with MongoDB support using grammars. 14 | 15 | I am currently looking for contributors and reviewers to get this package ready for production so that it can be integrated in [laravel-mongodb](https://github.com/jenssegers/laravel-mongodb). 16 | 17 | ### How can I contribute? 18 | 19 | #### 1. Reviewing 20 | 21 | Review code being pushed to this repository, and create issues to discuss if you may have found a better way to solve a certain functionality. 22 | 23 | #### 2. Writing Tests 24 | 25 | Tests are important to make sure this package remains stable during its course of development. If you want a new feature, or think something is not working like it should, please add a test proving the correct functionality, so that me or others can provide the correct implementation. 26 | 27 | #### 3. Pull Requests 28 | 29 | Pull requests are more than welcome to speed up the development of the package. Currently, there are quite some methods in [src/Query/Grammars/MongoGrammar.php](https://github.com/jenssegers/laravel-mongodb-core/blob/master/src/Query/Grammars/MongoGrammar.php) that throw a `not yet implemented` exception. I think implementing these are a great easy way to contribute! 30 | 31 | #### 4. Documentation 32 | 33 | This package provides quite some functionality, and documenting it will be a challenge. Contributions to add and/or improve documentation are certainly welcome! 34 | 35 | Installation 36 | ------------ 37 | 38 | Make sure you have the MongoDB PHP driver installed. You can find installation instructions [here](http://php.net/manual/en/mongodb.installation.php). 39 | 40 | Install the package using composer: 41 | 42 | ``` 43 | composer require jenssegers/mongodb-core 44 | ``` 45 | 46 | Testing 47 | ------- 48 | 49 | The tests can be run inside a [Docker](https://www.docker.com/community-edition#/download) container using: 50 | 51 | ``` 52 | make test 53 | ``` 54 | -------------------------------------------------------------------------------- /src/MongoConnectionAliasMethods.php: -------------------------------------------------------------------------------- 1 | query()->from($collection); 20 | } 21 | 22 | /** 23 | * Begin a fluent query against a database table. 24 | * 25 | * @param string $table 26 | * @return QueryBuilder 27 | */ 28 | public function table($table) 29 | { 30 | return $this->collection($table); 31 | } 32 | 33 | /** 34 | * @return Client 35 | */ 36 | public function getClient() 37 | { 38 | return $this->getPdo(); 39 | } 40 | 41 | /** 42 | * Get the current Client connection used for reading. 43 | * 44 | * @return Client 45 | */ 46 | public function getReadClient() 47 | { 48 | return $this->getReadPdo(); 49 | } 50 | 51 | /** 52 | * @param Client|Closure|null $client 53 | * @return Connection 54 | */ 55 | public function setClient($client) 56 | { 57 | return $this->setPdo($client); 58 | } 59 | 60 | /** 61 | * Set the Client connection used for reading. 62 | * 63 | * @param Client|Closure|null $client 64 | * @return Connection 65 | */ 66 | public function setReadClient($client) 67 | { 68 | return $this->setReadPdo($client); 69 | } 70 | 71 | /** 72 | * @return Client 73 | */ 74 | public function getPdo() 75 | { 76 | return parent::getPdo(); 77 | } 78 | 79 | /** 80 | * @return Client 81 | */ 82 | public function getReadPdo() 83 | { 84 | return parent::getReadPdo(); 85 | } 86 | 87 | /** 88 | * @param Closure|null|Client $client 89 | * @return Connection 90 | */ 91 | public function setPdo($client) 92 | { 93 | return parent::setPdo($client); 94 | } 95 | 96 | /** 97 | * @param Closure|null|Client $client 98 | * @return Connection 99 | */ 100 | public function setReadPdo($client) 101 | { 102 | return parent::setReadPdo($client); 103 | } 104 | 105 | /** 106 | * Get the PDO connection to use for a select query. 107 | * 108 | * @param bool $useReadClient 109 | * @return Client 110 | */ 111 | protected function getClientForSelect($useReadClient = true) 112 | { 113 | return $useReadClient ? $this->getReadClient() : $this->getClient(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Query/Builder.php: -------------------------------------------------------------------------------- 1 | where('_id', '=', $id)->first($columns); 66 | } 67 | 68 | /** 69 | * @inheritdoc 70 | */ 71 | public function delete($id = null) 72 | { 73 | // If an ID is passed to the method, we will set the where clause to check the 74 | // ID to let developers to simply and quickly remove a single row from this 75 | // database without manually specifying the "where" clauses on the query. 76 | if (null !== $id) { 77 | $this->where('_id', '=', $id); 78 | } 79 | 80 | return parent::delete(); 81 | } 82 | 83 | /** 84 | * @inheritdoc 85 | */ 86 | public function truncate() 87 | { 88 | $this->connection->statement($this->grammar->compileTruncate($this)); 89 | } 90 | 91 | /** 92 | * @param string $column 93 | * @param array $expression 94 | * @return $this 95 | */ 96 | public function addField($column, array $expression) 97 | { 98 | $this->addFields[] = compact('column', 'expression'); 99 | 100 | return $this; 101 | } 102 | 103 | /** 104 | * @param array $projection 105 | * @return $this 106 | */ 107 | public function project($column, $projection) 108 | { 109 | $this->projections[] = compact('column', 'projection'); 110 | 111 | return $this; 112 | } 113 | 114 | /** 115 | * @param array $sql 116 | * @param mixed $bindings 117 | * @param string $boolean 118 | * @return $this 119 | */ 120 | public function whereRaw($sql, $bindings = [], $boolean = 'and') 121 | { 122 | return parent::whereRaw($sql, $bindings, $boolean); 123 | } 124 | 125 | /** 126 | * @inheritdoc 127 | */ 128 | public function whereBetween($column, array $values, $boolean = 'and', $not = false) 129 | { 130 | $type = 'between'; 131 | 132 | $this->wheres[] = compact('column', 'type', 'values', 'boolean', 'not'); 133 | 134 | return $this; 135 | } 136 | 137 | /** 138 | * Append one or more values to a list. 139 | * 140 | * @param mixed $column 141 | * @param mixed $value 142 | * @param bool $unique 143 | * @return int 144 | */ 145 | public function push($column, $value = null, $unique = false) 146 | { 147 | return $this->connection->affectingStatement( 148 | $this->grammar->compilePush($this, $column, $value, $unique) 149 | ); 150 | } 151 | 152 | /** 153 | * Remove one or more values from a list. 154 | * 155 | * @param mixed $column 156 | * @param mixed $value 157 | * @return int 158 | */ 159 | public function pull($column, $value = null) 160 | { 161 | return $this->connection->affectingStatement( 162 | $this->grammar->compilePull($this, $column, $value) 163 | ); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/Connection.php: -------------------------------------------------------------------------------- 1 | schemaGrammar) { 53 | $this->useDefaultSchemaGrammar(); 54 | } 55 | 56 | return new SchemaBuilder($this); 57 | } 58 | 59 | /** 60 | * @return QueryBuilder 61 | */ 62 | public function query() 63 | { 64 | return new QueryBuilder( 65 | $this, $this->getQueryGrammar(), $this->getPostProcessor() 66 | ); 67 | } 68 | 69 | /** 70 | * @param callable $query 71 | * @param array $bindings 72 | * @return bool|mixed 73 | */ 74 | public function statement($query, $bindings = []) 75 | { 76 | return $this->run($query, $bindings, function ($query) { 77 | if ($this->pretending()) { 78 | return true; 79 | } 80 | 81 | $this->recordsHaveBeenModified(); 82 | 83 | return $query($this->getClient()->selectDatabase($this->database)); 84 | }); 85 | } 86 | 87 | /** 88 | * @inheritdoc 89 | */ 90 | public function affectingStatement($query, $bindings = []) 91 | { 92 | return $this->run($query, $bindings, function ($query) { 93 | if ($this->pretending()) { 94 | return 0; 95 | } 96 | 97 | $count = $query($this->getClient()->selectDatabase($this->database)); 98 | 99 | $this->recordsHaveBeenModified($count > 0); 100 | 101 | return $count; 102 | }); 103 | } 104 | 105 | /** 106 | * @inheritdoc 107 | */ 108 | public function select($query, $bindings = [], $useReadClient = true) 109 | { 110 | return $this->run($query, $bindings, function ($query) use ($useReadClient) { 111 | if ($this->pretending()) { 112 | return []; 113 | } 114 | 115 | return $query($this->getClientForSelect($useReadClient)->selectDatabase($this->database)); 116 | }); 117 | } 118 | 119 | /** 120 | * @inheritdoc 121 | */ 122 | protected function runQueryCallback($query, $bindings, Closure $callback) 123 | { 124 | // To execute the statement, we'll simply call the callback, which will actually 125 | // run the SQL against the PDO connection. Then we can calculate the time it 126 | // took to execute and log the query SQL, bindings and time in our memory. 127 | try { 128 | $result = $callback($query, $bindings); 129 | } catch (Exception $e) { 130 | // If an exception occurs when attempting to run a query, we'll format the error 131 | // message to include the bindings with SQL, which will make this exception a 132 | // lot more helpful to the developer instead of just the database's errors. 133 | throw new QueryException( 134 | $e->getMessage(), $this->prepareBindings($bindings), $e 135 | ); 136 | } 137 | 138 | return $result; 139 | } 140 | 141 | /** 142 | * @inheritdoc 143 | */ 144 | public function getDoctrineConnection() 145 | { 146 | throw new RuntimeException('Not supported'); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/Query/Grammars/MongoGrammar.php: -------------------------------------------------------------------------------- 1 | 'ne', 61 | '<>' => 'ne', 62 | '<' => 'lt', 63 | '<=' => 'lte', 64 | '>' => 'gt', 65 | '>=' => 'gte', 66 | ]; 67 | 68 | public function __construct() 69 | { 70 | // Add additional components that should be compiled. 71 | $this->selectComponents = array_merge($this->selectComponents, ['projections', 'addFields']); 72 | } 73 | 74 | /** 75 | * @inheritdoc 76 | */ 77 | public function compileSelect(Builder $query) 78 | { 79 | $components = $this->compileComponents($query); 80 | 81 | $options = [ 82 | 'typeMap' => ['array' => 'array'], 83 | ]; 84 | 85 | if (!empty($components['options'])) { 86 | $options = $components['options']; 87 | } 88 | 89 | $pipeline = $this->compilePipeline($components); 90 | 91 | return function (Database $database) use ($query, $pipeline, $options) { 92 | return $database->selectCollection($query->from)->aggregate($pipeline, $options); 93 | }; 94 | } 95 | 96 | /** 97 | * @param array $components 98 | * @return array 99 | */ 100 | protected function compilePipeline(array $components) 101 | { 102 | $pipeline = []; 103 | 104 | if (!empty($components['addFields'])) { 105 | $pipeline[] = ['$addFields' => $components['addFields']]; 106 | } 107 | if (!empty($components['columns']) && empty($components['groups'])) { 108 | $pipeline[] = ['$project' => $components['columns']]; 109 | } 110 | if (!empty($components['wheres'])) { 111 | $pipeline[] = ['$match' => $components['wheres']]; 112 | } 113 | if (!empty($components['aggregate'])) { 114 | $pipeline[] = [ 115 | '$group' => [ 116 | '_id' => null, 117 | 'aggregate' => $components['aggregate'], 118 | ], 119 | ]; 120 | } 121 | if (!empty($components['projections']) && empty($components['groups'])) { 122 | $pipeline[] = ['$project' => $components['projections']]; 123 | } 124 | if (!empty($components['groups'])) { 125 | $pipeline[] = ['$group' => $components['groups']]; 126 | } 127 | if (!empty($components['orders'])) { 128 | $pipeline[] = ['$sort' => $components['orders']]; 129 | } 130 | if (!empty($components['offset'])) { 131 | $pipeline[] = ['$skip' => $components['offset']]; 132 | } 133 | if (!empty($components['limit'])) { 134 | $pipeline[] = ['$limit' => $components['limit']]; 135 | } 136 | 137 | return $pipeline; 138 | } 139 | 140 | /** 141 | * @inheritdoc 142 | */ 143 | protected function compileAggregate(Builder $query, $aggregate) 144 | { 145 | if (in_array('*', $aggregate['columns'])) { 146 | $aggregate['columns'] = []; 147 | } 148 | 149 | if ($aggregate['function'] === 'count') { 150 | // If we have passed a column to the count method, we should only count the rows for which this 151 | // column is not null. 152 | if ($aggregate['columns']) { 153 | $query->whereNotNull(reset($aggregate['columns'])); 154 | } 155 | 156 | return ['$sum' => 1]; 157 | } 158 | 159 | return ['$' . $aggregate['function'] => '$' . reset($aggregate['columns'])]; 160 | } 161 | 162 | /** 163 | * @param Builder $query 164 | * @param array $projections 165 | * @return mixed 166 | */ 167 | protected function compileProjections(Builder $query, $projections) 168 | { 169 | $compiled = []; 170 | 171 | foreach ($projections as $projection) { 172 | $compiled[$projection['column']] = $projection['projection']; 173 | } 174 | 175 | return $compiled; 176 | } 177 | 178 | /** 179 | * @param Builder $query 180 | * @param array $fields 181 | * @return array 182 | */ 183 | protected function compileAddFields(Builder $query, $fields) 184 | { 185 | if (empty($fields)) { 186 | return []; 187 | } 188 | 189 | $compiled = []; 190 | 191 | foreach ($fields as $field) { 192 | $compiled[$field['column']] = $field['expression']; 193 | } 194 | 195 | return $compiled; 196 | } 197 | 198 | /** 199 | * @inheritdoc 200 | */ 201 | protected function compileColumns(Builder $query, $columns) 202 | { 203 | if (in_array('*', $columns)) { 204 | return []; 205 | } 206 | 207 | $compiled = []; 208 | 209 | foreach ($columns as $column) { 210 | if (false !== stripos($column, ' as ')) { 211 | [$original, $alias] = explode(' as ', strtolower($column)); 212 | $compiled[$alias] = '$' . $original; 213 | } else { 214 | $compiled[$column] = 1; 215 | } 216 | } 217 | 218 | return $compiled; 219 | } 220 | 221 | /** 222 | * @inheritdoc 223 | */ 224 | protected function compileFrom(Builder $query, $table) 225 | { 226 | return $table; 227 | } 228 | 229 | /** 230 | * @inheritdoc 231 | */ 232 | protected function compileJoins(Builder $query, $joins) 233 | { 234 | throw new RuntimeException(__FUNCTION__ . ' not yet implemented'); 235 | } 236 | 237 | /** 238 | * @inheritdoc 239 | */ 240 | protected function compileWheres(Builder $query) 241 | { 242 | if ($query->wheres === null) { 243 | return []; 244 | } 245 | 246 | if (empty($query->wheres)) { 247 | return []; 248 | } 249 | 250 | $compiled = []; 251 | 252 | foreach ($query->wheres as $i => $where) { 253 | $result = $this->{"where{$where['type']}"}($query, $where); 254 | $compiled[key($result)] = current($result); 255 | } 256 | 257 | return $compiled; 258 | } 259 | 260 | /** 261 | * @inheritdoc 262 | */ 263 | protected function whereRaw(Builder $query, $where) 264 | { 265 | return $where['sql']; 266 | } 267 | 268 | /** 269 | * @inheritdoc 270 | */ 271 | protected function whereBasic(Builder $query, $where) 272 | { 273 | $where['operator'] = ltrim($where['operator'], '$'); 274 | 275 | if ($where['operator'] === 'like') { 276 | // Convert to regular expression. 277 | $regex = preg_replace('#(^|[^\\\])%#', '$1.*', preg_quote($where['value'], '#')); 278 | 279 | // Convert like to regular expression. 280 | if (!Str::startsWith($where['value'], '%')) { 281 | $regex = '^' . $regex; 282 | } 283 | if (!Str::endsWith($where['value'], '%')) { 284 | $regex .= '$'; 285 | } 286 | 287 | $where['value'] = new Regex($regex, 'i'); 288 | $where['operator'] = '='; 289 | } 290 | 291 | if (in_array($where['operator'], ['regexp', 'not regexp', 'regex', 'not regex'])) { 292 | // Automatically convert regular expression strings to Regex objects. 293 | if (!$where['value'] instanceof Regex) { 294 | [, $regex, $flags] = explode($where['value'][0], $where['value']); 295 | $where['value'] = new Regex($regex, $flags); 296 | } 297 | 298 | // For inverse regexp operations, we can just use the $not operator. 299 | if (Str::startsWith($where['operator'], 'not')) { 300 | $where['operator'] = 'not'; 301 | } else { 302 | $where['operator'] = '='; 303 | } 304 | } 305 | 306 | // Convert operators into MongoDB operations. 307 | if (array_key_exists($where['operator'], $this->operatorConversion)) { 308 | $where['operator'] = $this->operatorConversion[$where['operator']]; 309 | } 310 | 311 | if ($where['operator'] === '=') { 312 | return [$where['column'] => $where['value']]; 313 | } 314 | 315 | return [$where['column'] => ['$' . $where['operator'] => $where['value']]]; 316 | } 317 | 318 | /** 319 | * @inheritdoc 320 | */ 321 | protected function whereIn(Builder $query, $where) 322 | { 323 | return [$where['column'] => ['$in' => $where['values']]]; 324 | } 325 | 326 | /** 327 | * @inheritdoc 328 | */ 329 | protected function whereNotIn(Builder $query, $where) 330 | { 331 | return [$where['column'] => ['$nin' => $where['values']]]; 332 | } 333 | 334 | /** 335 | * @inheritdoc 336 | */ 337 | protected function whereInSub(Builder $query, $where) 338 | { 339 | throw new RuntimeException(__FUNCTION__ . ' not yet implemented'); 340 | } 341 | 342 | /** 343 | * @inheritdoc 344 | */ 345 | protected function whereNotInSub(Builder $query, $where) 346 | { 347 | throw new RuntimeException(__FUNCTION__ . ' not yet implemented'); 348 | } 349 | 350 | /** 351 | * @inheritdoc 352 | */ 353 | protected function whereNull(Builder $query, $where) 354 | { 355 | return [$where['column'] => null]; 356 | } 357 | 358 | /** 359 | * @inheritdoc 360 | */ 361 | protected function whereNotNull(Builder $query, $where) 362 | { 363 | return [$where['column'] => ['$ne' => null]]; 364 | } 365 | 366 | /** 367 | * @inheritdoc 368 | */ 369 | protected function whereBetween(Builder $query, $where) 370 | { 371 | if ($where['not']) { 372 | return [ 373 | '$or' => [ 374 | [ 375 | $where['column'] => [ 376 | '$lt' => $where['values'][0], 377 | ], 378 | ], 379 | [ 380 | $where['column'] => [ 381 | '$gt' => $where['values'][1], 382 | ], 383 | ], 384 | ], 385 | ]; 386 | } 387 | 388 | return [ 389 | $where['column'] => [ 390 | '$gte' => $where['values'][0], 391 | '$lte' => $where['values'][1], 392 | ], 393 | ]; 394 | } 395 | 396 | /** 397 | * @inheritdoc 398 | */ 399 | protected function dateBasedWhere($type, Builder $query, $where) 400 | { 401 | switch (strtolower($type)) { 402 | case 'day': 403 | $expression = ['$dayOfMonth' => '$' . $where['column']]; 404 | $where['value'] = (int) $where['value']; 405 | break; 406 | 407 | case 'month': 408 | case 'year': 409 | $expression = ['$' . strtolower($type) => '$' . $where['column']]; 410 | $where['value'] = (int) $where['value']; 411 | break; 412 | 413 | case 'date': 414 | $expression = [ 415 | '$dateToString' => [ 416 | 'format' => '%Y-%m-%d', 417 | 'date' => '$' . $where['column'], 418 | ], 419 | ]; 420 | break; 421 | 422 | case 'time': 423 | $expression = [ 424 | '$dateToString' => [ 425 | 'format' => '%H:%M:%S', 426 | 'date' => '$' . $where['column'], 427 | ], 428 | ]; 429 | break; 430 | } 431 | 432 | return [ 433 | '$expr' => [ 434 | '$eq' => [ 435 | $expression, 436 | $where['value'], 437 | ], 438 | ], 439 | ]; 440 | } 441 | 442 | /** 443 | * @inheritdoc 444 | */ 445 | protected function whereColumn(Builder $query, $where) 446 | { 447 | throw new RuntimeException(__FUNCTION__ . ' not yet implemented'); 448 | } 449 | 450 | /** 451 | * @inheritdoc 452 | */ 453 | protected function whereNested(Builder $query, $where) 454 | { 455 | throw new RuntimeException(__FUNCTION__ . ' not yet implemented'); 456 | } 457 | 458 | /** 459 | * @inheritdoc 460 | */ 461 | protected function whereSub(Builder $query, $where) 462 | { 463 | throw new RuntimeException(__FUNCTION__ . ' not yet implemented'); 464 | } 465 | 466 | /** 467 | * @inheritdoc 468 | */ 469 | protected function whereExists(Builder $query, $where) 470 | { 471 | throw new RuntimeException(__FUNCTION__ . ' not yet implemented'); 472 | } 473 | 474 | /** 475 | * @inheritdoc 476 | */ 477 | protected function whereNotExists(Builder $query, $where) 478 | { 479 | throw new RuntimeException(__FUNCTION__ . ' not yet implemented'); 480 | } 481 | 482 | /** 483 | * @inheritdoc 484 | */ 485 | protected function compileGroups(Builder $query, $groups) 486 | { 487 | $compiled = []; 488 | foreach ($groups as $group) { 489 | $compiled['_id'][$group] = '$' . $group; 490 | $compiled[$group] = ['$last' => '$' . $group]; 491 | } 492 | 493 | foreach ($query->columns as $group) { 494 | // This mimics MySQL's behaviour where we select the last value of every selected column. 495 | $compiled[$group] = ['$last' => '$' . $group]; 496 | } 497 | 498 | return $compiled; 499 | } 500 | 501 | /** 502 | * @inheritdoc 503 | */ 504 | protected function compileHavings(Builder $query, $havings) 505 | { 506 | throw new RuntimeException(__FUNCTION__ . ' not yet implemented'); 507 | } 508 | 509 | /** 510 | * @inheritdoc 511 | */ 512 | protected function compileHaving(array $having) 513 | { 514 | throw new RuntimeException(__FUNCTION__ . ' not yet implemented'); 515 | } 516 | 517 | /** 518 | * @inheritdoc 519 | */ 520 | protected function compileBasicHaving($having) 521 | { 522 | throw new RuntimeException(__FUNCTION__ . ' not yet implemented'); 523 | } 524 | 525 | /** 526 | * @inheritdoc 527 | */ 528 | protected function compileOrders(Builder $query, $orders) 529 | { 530 | $compiled = []; 531 | 532 | foreach ($orders as $order) { 533 | if (is_string($order['direction'])) { 534 | $order['direction'] = strtolower($order['direction']) === 'asc' ? 1 : -1; 535 | } 536 | 537 | if ($order['column'] === 'natural') { 538 | $compiled['$natural'] = $order['direction']; 539 | } else { 540 | $compiled[$order['column']] = $order['direction']; 541 | } 542 | } 543 | 544 | return $compiled; 545 | } 546 | 547 | /** 548 | * @inheritdoc 549 | */ 550 | public function compileRandom($seed) 551 | { 552 | throw new RuntimeException(__FUNCTION__ . ' not yet implemented'); 553 | } 554 | 555 | /** 556 | * @inheritdoc 557 | */ 558 | protected function compileLimit(Builder $query, $limit) 559 | { 560 | return (int) $limit; 561 | } 562 | 563 | /** 564 | * @inheritdoc 565 | */ 566 | protected function compileOffset(Builder $query, $offset) 567 | { 568 | return (int) $offset; 569 | } 570 | 571 | /** 572 | * @inheritdoc 573 | */ 574 | protected function compileUnions(Builder $query) 575 | { 576 | throw new RuntimeException(__FUNCTION__ . ' not yet implemented'); 577 | } 578 | 579 | /** 580 | * @inheritdoc 581 | */ 582 | protected function compileUnion(array $union) 583 | { 584 | throw new RuntimeException(__FUNCTION__ . ' not yet implemented'); 585 | } 586 | 587 | /** 588 | * @inheritdoc 589 | */ 590 | public function compileExists(Builder $query) 591 | { 592 | throw new RuntimeException(__FUNCTION__ . ' not yet implemented'); 593 | } 594 | 595 | /** 596 | * @param Builder $query 597 | * @param array $values 598 | * @return Closure 599 | */ 600 | public function compileInsert(Builder $query, array $values) 601 | { 602 | return function (Database $database) use ($query, $values) { 603 | $result = $database->selectCollection($query->from)->insertMany($values); 604 | 605 | return $result->isAcknowledged(); 606 | }; 607 | } 608 | 609 | /** 610 | * @param Builder $query 611 | * @param array $values 612 | * @param string $sequence 613 | * @return Closure 614 | */ 615 | public function compileInsertGetId(Builder $query, $values, $sequence) 616 | { 617 | return function (Database $database) use ($query, $values) { 618 | $result = $database->selectCollection($query->from)->insertOne($values); 619 | 620 | return $result->getInsertedId(); 621 | }; 622 | } 623 | 624 | /** 625 | * @param Builder $query 626 | * @param array $values 627 | * @return Closure 628 | */ 629 | public function compileUpdate(Builder $query, $values) 630 | { 631 | $wheres = $this->compileWheres($query); 632 | 633 | // Use $set as default operator. 634 | if (substr(key($values), 0, 1) !== '$') { 635 | $values = ['$set' => $values]; 636 | } 637 | 638 | return function (Database $database) use ($query, $wheres, $values) { 639 | $result = $database->selectCollection($query->from)->updateMany($wheres, $values); 640 | 641 | return $result->getModifiedCount(); 642 | }; 643 | } 644 | 645 | /** 646 | * @param Builder $query 647 | * @return Closure 648 | */ 649 | public function compileDelete(Builder $query) 650 | { 651 | $wheres = $this->compileWheres($query); 652 | 653 | return function (Database $database) use ($query, $wheres) { 654 | $result = $database->selectCollection($query->from)->deleteMany($wheres); 655 | 656 | return $result->getDeletedCount(); 657 | }; 658 | } 659 | 660 | /** 661 | * @param Builder $query 662 | * @return Closure 663 | */ 664 | public function compileTruncate(Builder $query) 665 | { 666 | return function (Database $database) use ($query) { 667 | return $database->selectCollection($query->from)->drop(); 668 | }; 669 | } 670 | 671 | /** 672 | * @inheritdoc 673 | */ 674 | protected function compileLock(Builder $query, $value) 675 | { 676 | throw new RuntimeException(__FUNCTION__ . ' not yet implemented'); 677 | } 678 | 679 | /** 680 | * @param Builder $query 681 | * @param mixed $column 682 | * @param mixed $value 683 | * @param bool $unique 684 | * @return Closure 685 | */ 686 | public function compilePush(Builder $query, $column, $value, $unique) 687 | { 688 | // Use the addToSet operator in case we only want unique items. 689 | $operator = $unique ? '$addToSet' : '$push'; 690 | 691 | // Check if we are pushing multiple values. 692 | $batch = (is_array($value) && array_keys($value) === range(0, count($value) - 1)); 693 | 694 | if (is_array($column)) { 695 | $compiled = [$operator => $column]; 696 | } elseif ($batch) { 697 | $compiled = [$operator => [$column => ['$each' => $value]]]; 698 | } else { 699 | $compiled = [$operator => [$column => $value]]; 700 | } 701 | 702 | return $this->compileUpdate($query, $compiled); 703 | } 704 | 705 | /** 706 | * @param Builder $query 707 | * @param mixed $column 708 | * @param mixed $value 709 | * @return Closure 710 | */ 711 | public function compilePull(Builder $query, $column, $value) 712 | { 713 | // Check if we are pushing multiple values. 714 | $batch = (is_array($value) && array_keys($value) === range(0, count($value) - 1)); 715 | 716 | // If we are pulling multiple values, we need to use $pullAll. 717 | $operator = $batch ? '$pullAll' : '$pull'; 718 | 719 | if (is_array($column)) { 720 | $compiled = [$operator => $column]; 721 | } else { 722 | $compiled = [$operator => [$column => $value]]; 723 | } 724 | 725 | return $this->compileUpdate($query, $compiled); 726 | } 727 | 728 | /** 729 | * @inheritdoc 730 | */ 731 | public function getOperators() 732 | { 733 | return array_map('strtolower', $this->operators); 734 | } 735 | 736 | /** 737 | * @inheritdoc 738 | */ 739 | public function supportsSavepoints() 740 | { 741 | return false; 742 | } 743 | } 744 | -------------------------------------------------------------------------------- /tests/Query/BuilderTest.php: -------------------------------------------------------------------------------- 1 | db = $this->app->get('db'); 26 | $this->db->collection('users')->truncate(); 27 | } 28 | 29 | public function testBuilderInstance() 30 | { 31 | $this->assertInstanceOf(Builder::class, $this->db->collection('users')); 32 | } 33 | 34 | public function testInsert() 35 | { 36 | $this->db->collection('users')->insert([ 37 | 'name' => 'John Doe', 38 | ]); 39 | 40 | $this->assertEquals(1, $this->db->collection('users')->count()); 41 | } 42 | 43 | public function testGet() 44 | { 45 | $results = $this->db->collection('users')->get(); 46 | $this->assertCount(0, $results); 47 | 48 | $this->db->collection('users')->insert([ 49 | [ 50 | 'name' => 'John Doe', 51 | 'tags' => ['tag1', 'tag2'], 52 | ], 53 | ]); 54 | 55 | $results = $this->db->collection('users')->get(); 56 | $this->assertInstanceOf(Collection::class, $results); 57 | $this->assertCount(1, $results); 58 | $this->assertEquals('John Doe', $results[0]->name); 59 | $this->assertEquals(['tag1', 'tag2'], $results[0]->tags); 60 | 61 | $results = $this->db->collection('users')->where('foo', 'bar')->get(); 62 | $this->assertCount(0, $results); 63 | } 64 | 65 | public function testInsertGetId() 66 | { 67 | $id = $this->db->collection('users')->insertGetId(['name' => 'Jane Doe']); 68 | $this->assertInstanceOf(ObjectId::class, $id); 69 | } 70 | 71 | public function testBatchInsert() 72 | { 73 | $this->db->collection('users')->insert([ 74 | ['name' => 'John Doe'], 75 | ['name' => 'Jane Doe'], 76 | ]); 77 | 78 | $this->assertEquals(2, $this->db->collection('users')->count()); 79 | } 80 | 81 | public function testWhere() 82 | { 83 | $this->db->collection('users')->insert([ 84 | ['name' => 'Jane Doe', 'age' => 20], 85 | ['name' => 'John Doe', 'age' => 21], 86 | ['name' => 'Mark Moe', 'age' => 20], 87 | ]); 88 | 89 | $result = $this->db->collection('users')->where('age', 20)->get(); 90 | $this->assertCount(2, $result); 91 | 92 | $result = $this->db->collection('users')->get(['name']); 93 | $this->assertCount(3, $result); 94 | 95 | $result = $this->db->collection('users')->where('age', 22)->get(); 96 | $this->assertCount(0, $result); 97 | } 98 | 99 | public function testWhereNested() 100 | { 101 | $this->db->collection('users')->insert([ 102 | ['name' => 'John Doe', 'address' => ['country' => 'France', 'city' => 'Paris']], 103 | ['name' => 'Jane Doe', 'address' => ['country' => 'Belgium', 'city' => 'Ghent']], 104 | ]); 105 | 106 | $result = $this->db->collection('users')->where('address.country', 'Belgium')->get(); 107 | $this->assertCount(1, $result); 108 | $this->assertEquals('Jane Doe', $result[0]->name); 109 | } 110 | 111 | public function testWhereList() 112 | { 113 | $this->db->collection('users')->insert([ 114 | ['name' => 'Jane Doe', 'tags' => ['tag1', 'tag2']], 115 | ['name' => 'John Doe', 'tags' => ['tag3', 'tag2']], 116 | ]); 117 | 118 | $result = $this->db->collection('users')->where('tags', 'tag1')->get(); 119 | $this->assertCount(1, $result); 120 | 121 | $result = $this->db->collection('users')->where('tags', 'tag2')->get(); 122 | $this->assertCount(2, $result); 123 | } 124 | 125 | public function testFind() 126 | { 127 | $id = $this->db->collection('users')->insertGetId(['name' => 'John Doe']); 128 | 129 | $result = $this->db->collection('users')->find($id); 130 | $this->assertEquals('John Doe', $result->name); 131 | } 132 | 133 | public function testFindNull() 134 | { 135 | $result = $this->db->collection('users')->find(null); 136 | $this->assertNull($result); 137 | } 138 | 139 | public function testCount() 140 | { 141 | $this->db->collection('users')->insert([ 142 | ['name' => 'Jane Doe', 'age' => 20], 143 | ['name' => 'John Doe', 'age' => null], 144 | ['name' => 'Mark Moe', 'age' => 21], 145 | ]); 146 | 147 | $this->assertEquals(3, $this->db->collection('users')->count()); 148 | $this->assertEquals(2, $this->db->collection('users')->count('age')); 149 | $this->assertEquals(1, $this->db->collection('users')->where('age', null)->count()); 150 | $this->assertEquals(2, $this->db->collection('users')->where('age', '>', 10)->count()); 151 | } 152 | 153 | public function testUpdate() 154 | { 155 | $this->db->collection('users')->insert([ 156 | ['name' => 'Jane Doe', 'age' => 20], 157 | ['name' => 'John Doe', 'age' => 21], 158 | ['name' => 'Mark Moe', 'age' => 20], 159 | ]); 160 | 161 | $this->db->collection('users')->where('name', 'Jane Doe')->update(['age' => 21]); 162 | 163 | $result = $this->db->collection('users')->where('name', 'Jane Doe')->first(); 164 | $this->assertEquals(21, $result->age); 165 | 166 | $result = $this->db->collection('users')->where('name', 'Mark Moe')->first(); 167 | $this->assertEquals(20, $result->age); 168 | } 169 | 170 | public function testFirst() 171 | { 172 | $result = $this->db->collection('users')->first(); 173 | $this->assertEquals(null, $result); 174 | 175 | $this->db->collection('users')->insert([ 176 | ['name' => 'Jane Doe', 'age' => 20], 177 | ['name' => 'John Doe', 'age' => 21], 178 | ['name' => 'Mark Moe', 'age' => 20], 179 | ]); 180 | 181 | $result = $this->db->collection('users')->first(); 182 | $this->assertEquals('Jane Doe', $result->name); 183 | 184 | $result = $this->db->collection('users')->where('foo', 'bar')->first(); 185 | $this->assertNull($result); 186 | 187 | $result = $this->db->collection('users')->where('age', 21)->first(); 188 | $this->assertEquals('John Doe', $result->name); 189 | } 190 | 191 | public function testDelete() 192 | { 193 | $this->db->collection('users')->insert([ 194 | ['name' => 'Jane Doe'], 195 | ['name' => 'John Doe'], 196 | ['name' => 'Mark Moe'], 197 | ['name' => 'Larry Loe'], 198 | ]); 199 | 200 | $count = $this->db->collection('users')->where('name', 'Foo Bar')->delete(); 201 | $this->assertEquals(0, $count); 202 | $this->assertEquals(4, $this->db->collection('users')->count()); 203 | 204 | $count = $this->db->collection('users')->where('name', 'John Doe')->delete(); 205 | $this->assertEquals(1, $count); 206 | $this->assertEquals(3, $this->db->collection('users')->count()); 207 | 208 | $result = $this->db->collection('users')->where('name', 'John Doe')->first(); 209 | $this->assertNull($result); 210 | 211 | $first = $this->db->collection('users')->first(); 212 | $count = $this->db->collection('users')->delete($first->_id); 213 | $this->assertEquals(1, $count); 214 | $this->assertEquals(2, $this->db->collection('users')->count()); 215 | 216 | $count = $this->db->collection('users')->delete('abcd'); 217 | $this->assertEquals(0, $count); 218 | $this->assertEquals(2, $this->db->collection('users')->count()); 219 | 220 | $count = $this->db->collection('users')->delete(); 221 | $this->assertEquals(2, $count); 222 | $this->assertEquals(0, $this->db->collection('users')->count()); 223 | } 224 | 225 | public function testWhereRegex() 226 | { 227 | $this->db->collection('users')->insert([ 228 | ['name' => 'John Doe'], 229 | ['name' => 'Jane Doe'], 230 | ['name' => 'Robert Roe'], 231 | ]); 232 | 233 | $regex = new Regex('.*doe', 'i'); 234 | $results = $this->db->collection('users')->where('name', 'regex', $regex)->get(); 235 | $this->assertCount(2, $results); 236 | 237 | $regex = new Regex('.*doe', 'i'); 238 | $results = $this->db->collection('users')->where('name', 'regexp', $regex)->get(); 239 | $this->assertCount(2, $results); 240 | 241 | $results = $this->db->collection('users')->where('name', 'regexp', '/.*doe/i')->get(); 242 | $this->assertCount(2, $results); 243 | 244 | $results = $this->db->collection('users')->where('name', 'not regexp', '/.*doe/i')->get(); 245 | $this->assertCount(1, $results); 246 | } 247 | 248 | public function testWhereLike() 249 | { 250 | $this->db->collection('users')->insert([ 251 | ['name' => 'John Doe'], 252 | ['name' => 'Jane Doe'], 253 | ['name' => 'Robert Roe'], 254 | ]); 255 | 256 | $results = $this->db->collection('users')->where('name', 'like', '%doe%')->get(); 257 | $this->assertCount(2, $results); 258 | 259 | $results = $this->db->collection('users')->where('name', 'like', '%oe')->get(); 260 | $this->assertCount(3, $results); 261 | 262 | $results = $this->db->collection('users')->where('name', 'like', 'j%')->get(); 263 | $this->assertCount(2, $results); 264 | 265 | $results = $this->db->collection('users')->where('name', 'like', 'x')->get(); 266 | $this->assertCount(0, $results); 267 | } 268 | 269 | public function testCustomOperators() 270 | { 271 | $this->db->collection('users')->insert([ 272 | [ 273 | 'name' => 'John Doe', 274 | 'age' => 30, 275 | 'addresses' => [ 276 | ['city' => 'Ghent'], 277 | ['city' => 'Paris'], 278 | ], 279 | 'tags' => ['one', 'two'], 280 | ], 281 | [ 282 | 'name' => 'Jane Doe', 283 | 'addresses' => [ 284 | ['city' => 'Brussels'], 285 | ['city' => 'Paris'], 286 | ], 287 | 'tags' => ['one', 'two', 'three', 'four'], 288 | ], 289 | [ 290 | 'name' => 'Robert Roe', 291 | 'age' => 'thirty-one', 292 | 'tags' => ['three', 'four'], 293 | ], 294 | ]); 295 | 296 | $results = $this->db->collection('users')->where('age', 'exists', true)->get(); 297 | $this->assertCount(2, $results); 298 | 299 | $resultsNames = [$results[0]->name, $results[1]->name]; 300 | $this->assertContains('John Doe', $resultsNames); 301 | $this->assertContains('Robert Roe', $resultsNames); 302 | 303 | $results = $this->db->collection('users')->where('age', 'exists', false)->get(); 304 | $this->assertCount(1, $results); 305 | $this->assertEquals('Jane Doe', $results->first()->name); 306 | 307 | $results = $this->db->collection('users')->where('age', 'type', 2)->get(); 308 | $this->assertCount(1, $results); 309 | $this->assertEquals('Robert Roe', $results->first()->name); 310 | 311 | $results = $this->db->collection('users')->where('age', 'mod', [15, 0])->get(); 312 | $this->assertCount(1, $results); 313 | $this->assertEquals('John Doe', $results->first()->name); 314 | 315 | $results = $this->db->collection('users')->where('age', 'mod', [29, 1])->get(); 316 | $this->assertCount(1, $results); 317 | $this->assertEquals('John Doe', $results->first()->name); 318 | 319 | $results = $this->db->collection('users')->where('age', 'mod', [14, 0])->get(); 320 | $this->assertCount(0, $results); 321 | 322 | $results = $this->db->collection('users')->where('tags', 'all', ['one', 'two'])->get(); 323 | $this->assertCount(2, $results); 324 | 325 | $results = $this->db->collection('users')->where('tags', 'all', ['one', 'three'])->get(); 326 | $this->assertCount(1, $results); 327 | 328 | $results = $this->db->collection('users')->where('tags', 'size', 2)->get(); 329 | $this->assertCount(2, $results); 330 | 331 | $results = $this->db->collection('users')->where('tags', 'size', 3)->get(); 332 | $this->assertCount(0, $results); 333 | 334 | $results = $this->db->collection('users')->where('tags', 'size', 4)->get(); 335 | $this->assertCount(1, $results); 336 | 337 | $results = $this->db->collection('users')->where('addresses', 'elemMatch', ['city' => 'Brussels'])->get(); 338 | $this->assertCount(1, $results); 339 | $this->assertEquals('Jane Doe', $results->first()->name); 340 | } 341 | 342 | public function testWhereIn() 343 | { 344 | $this->db->collection('users')->insert([ 345 | ['name' => 'Jane Doe', 'age' => 20], 346 | ['name' => 'John Doe', 'age' => 21], 347 | ['name' => 'Mark Moe', 'age' => 20], 348 | ]); 349 | 350 | $results = $this->db->collection('users')->whereIn('age', [20, 21])->get(); 351 | $this->assertCount(3, $results); 352 | 353 | $results = $this->db->collection('users')->whereIn('age', [20])->get(); 354 | $this->assertCount(2, $results); 355 | 356 | $results = $this->db->collection('users')->whereIn('age', [21])->get(); 357 | $this->assertCount(1, $results); 358 | 359 | $results = $this->db->collection('users')->whereIn('age', [])->get(); 360 | $this->assertCount(0, $results); 361 | } 362 | 363 | public function testWhereNotIn() 364 | { 365 | $this->db->collection('users')->insert([ 366 | ['name' => 'Jane Doe', 'age' => 20], 367 | ['name' => 'John Doe', 'age' => 21], 368 | ['name' => 'Mark Moe', 'age' => 20], 369 | ]); 370 | 371 | $results = $this->db->collection('users')->whereNotIn('age', [20, 21])->get(); 372 | $this->assertCount(0, $results); 373 | 374 | $results = $this->db->collection('users')->whereNotIn('age', [20])->get(); 375 | $this->assertCount(1, $results); 376 | 377 | $results = $this->db->collection('users')->whereNotIn('age', [21])->get(); 378 | $this->assertCount(2, $results); 379 | 380 | $results = $this->db->collection('users')->whereNotIn('age', [])->get(); 381 | $this->assertCount(3, $results); 382 | } 383 | 384 | public function testWhereNull() 385 | { 386 | $this->db->collection('users')->insert([ 387 | ['name' => 'Jane Doe', 'age' => 20], 388 | ['name' => 'John Doe', 'age' => null], 389 | ['name' => 'Mark Moe', 'age' => 20], 390 | ]); 391 | 392 | $results = $this->db->collection('users')->whereNull('age')->get(); 393 | $this->assertCount(1, $results); 394 | 395 | $results = $this->db->collection('users')->whereNull('foo')->get(); 396 | $this->assertCount(3, $results); 397 | 398 | $results = $this->db->collection('users')->whereNull('name')->get(); 399 | $this->assertCount(0, $results); 400 | } 401 | 402 | public function testWhereNotNull() 403 | { 404 | $this->db->collection('users')->insert([ 405 | ['name' => 'Jane Doe', 'age' => 20], 406 | ['name' => 'John Doe', 'age' => null], 407 | ['name' => 'Mark Moe', 'age' => 20], 408 | ]); 409 | 410 | $results = $this->db->collection('users')->whereNotNull('age')->get(); 411 | $this->assertCount(2, $results); 412 | 413 | $results = $this->db->collection('users')->whereNotNull('foo')->get(); 414 | $this->assertCount(0, $results); 415 | 416 | $results = $this->db->collection('users')->whereNotNull('name')->get(); 417 | $this->assertCount(3, $results); 418 | } 419 | 420 | public function testWhereBetween() 421 | { 422 | $this->db->collection('users')->insert([ 423 | ['name' => 'Jane Doe', 'age' => 20], 424 | ['name' => 'John Doe', 'age' => 30], 425 | ['name' => 'Mark Moe', 'age' => 25], 426 | ['name' => 'Larry Loe', 'age' => 40], 427 | ]); 428 | 429 | $results = $this->db->collection('users')->whereBetween('age', [19, 21])->get(); 430 | $this->assertCount(1, $results); 431 | 432 | $results = $this->db->collection('users')->whereBetween('age', [20, 20])->get(); 433 | $this->assertCount(1, $results); 434 | 435 | $results = $this->db->collection('users')->whereBetween('age', [20, 30])->get(); 436 | $this->assertCount(3, $results); 437 | 438 | $results = $this->db->collection('users')->whereBetween('age', [5, 10])->get(); 439 | $this->assertCount(0, $results); 440 | } 441 | 442 | public function testWhereNotBetween() 443 | { 444 | $this->db->collection('users')->insert([ 445 | ['name' => 'Jane Doe', 'age' => 20], 446 | ['name' => 'John Doe', 'age' => 30], 447 | ['name' => 'Mark Moe', 'age' => 25], 448 | ['name' => 'Larry Loe', 'age' => 40], 449 | ]); 450 | 451 | $results = $this->db->collection('users')->whereNotBetween('age', [19, 21])->get(); 452 | $this->assertCount(3, $results); 453 | 454 | $results = $this->db->collection('users')->whereNotBetween('age', [20, 20])->get(); 455 | $this->assertCount(3, $results); 456 | 457 | $results = $this->db->collection('users')->whereNotBetween('age', [20, 30])->get(); 458 | $this->assertCount(1, $results); 459 | 460 | $results = $this->db->collection('users')->whereNotBetween('age', [5, 10])->get(); 461 | $this->assertCount(4, $results); 462 | } 463 | 464 | public function testWhereDate() 465 | { 466 | $this->db->collection('users')->insert([ 467 | ['name' => 'Jane Doe', 'birthday' => new UTCDateTime(new DateTime('1990/01/01 10:00:00'))], 468 | ['name' => 'John Doe', 'birthday' => new UTCDateTime(new DateTime('1980/03/01 11:00:00'))], 469 | ['name' => 'Mark Moe', 'birthday' => new UTCDateTime(new DateTime('1970/03/01 12:00:00'))], 470 | ['name' => 'Larry Loe', 'birthday' => new UTCDateTime(new DateTime('1960/04/01 13:00:00'))], 471 | ]); 472 | 473 | $results = $this->db->collection('users')->whereYear('birthday', 1990)->get(); 474 | $this->assertCount(1, $results); 475 | 476 | $results = $this->db->collection('users')->whereDay('birthday', 1)->get(); 477 | $this->assertCount(4, $results); 478 | 479 | $results = $this->db->collection('users')->whereMonth('birthday', 3)->get(); 480 | $this->assertCount(2, $results); 481 | 482 | $results = $this->db->collection('users')->whereDate('birthday', '1970-03-01')->get(); 483 | $this->assertCount(1, $results); 484 | 485 | $results = $this->db->collection('users')->whereTime('birthday', '12:00:00')->get(); 486 | $this->assertCount(1, $results); 487 | } 488 | 489 | public function testWhereRaw() 490 | { 491 | $this->db->collection('users')->insert([ 492 | ['name' => 'Jane Doe', 'age' => 20], 493 | ['name' => 'John Doe', 'age' => 30], 494 | ['name' => 'Mark Moe', 'age' => 25], 495 | ['name' => 'Larry Loe', 'age' => 40], 496 | ]); 497 | 498 | $results = $this->db->collection('users')->whereRaw([ 499 | 'age' => ['$in' => [20, 30]], 500 | ])->get(); 501 | $this->assertCount(2, $results); 502 | } 503 | 504 | public function testAddFields() 505 | { 506 | $this->db->collection('users')->insert([ 507 | ['name' => 'Jane Doe', 'foo' => 1, 'bar' => 5], 508 | ['name' => 'John Doe', 'foo' => 2, 'bar' => 6], 509 | ['name' => 'Mark Moe', 'foo' => 3, 'bar' => 7], 510 | ['name' => 'Larry Loe', 'foo' => 4, 'bar' => 8], 511 | ]); 512 | 513 | $results = $this->db->collection('users') 514 | ->addField('sum', ['$add' => ['$foo', '$bar']]) 515 | ->addField('max', ['$max' => ['$foo', '$bar']]) 516 | ->get(); 517 | 518 | $this->assertEquals(6, $results->first()->sum); 519 | $this->assertEquals(5, $results->first()->max); 520 | } 521 | 522 | public function testLimit() 523 | { 524 | $this->db->collection('users')->insert([ 525 | ['name' => 'Jane Doe', 'age' => 20], 526 | ['name' => 'John Doe', 'age' => 30], 527 | ['name' => 'Mark Moe', 'age' => 25], 528 | ['name' => 'Larry Loe', 'age' => 40], 529 | ]); 530 | 531 | $results = $this->db->collection('users')->limit(1)->get(); 532 | $this->assertCount(1, $results); 533 | $this->assertEquals('Jane Doe', $results->first()->name); 534 | 535 | $results = $this->db->collection('users')->limit(2)->get(); 536 | $this->assertCount(2, $results); 537 | $this->assertEquals('John Doe', $results->last()->name); 538 | 539 | $results = $this->db->collection('users') 540 | ->whereBetween('age', [21, 100])->limit(2)->get(); 541 | $this->assertCount(2, $results); 542 | $this->assertEquals('John Doe', $results->first()->name); 543 | } 544 | 545 | public function testOffset() 546 | { 547 | $this->db->collection('users')->insert([ 548 | ['name' => 'Jane Doe', 'age' => 20], 549 | ['name' => 'John Doe', 'age' => 30], 550 | ['name' => 'Mark Moe', 'age' => 25], 551 | ['name' => 'Larry Loe', 'age' => 40], 552 | ]); 553 | 554 | $results = $this->db->collection('users')->offset(1)->get(); 555 | $this->assertCount(3, $results); 556 | $this->assertEquals('John Doe', $results->first()->name); 557 | 558 | $results = $this->db->collection('users')->offset(2)->get(); 559 | $this->assertCount(2, $results); 560 | $this->assertEquals('Mark Moe', $results->first()->name); 561 | 562 | $results = $this->db->collection('users')->offset(2)->limit(1)->get(); 563 | $this->assertCount(1, $results); 564 | $this->assertEquals('Mark Moe', $results->first()->name); 565 | } 566 | 567 | public function testOrderBy() 568 | { 569 | $this->db->collection('users')->insert([ 570 | ['name' => 'Jane Doe', 'age' => 30], 571 | ['name' => 'John Doe', 'age' => 20], 572 | ['name' => 'Mark Moe', 'age' => 25], 573 | ['name' => 'Larry Loe', 'age' => 40], 574 | ]); 575 | 576 | $results = $this->db->collection('users')->orderBy('age', 'asc')->get(); 577 | $this->assertEquals('John Doe', $results->first()->name); 578 | 579 | $results = $this->db->collection('users')->orderBy('age', 'desc')->get(); 580 | $this->assertEquals('Larry Loe', $results->first()->name); 581 | } 582 | 583 | public function testSelect() 584 | { 585 | $this->db->collection('users')->insert([ 586 | ['name' => 'Jane Doe', 'foo' => 30, 'bar' => 10], 587 | ['name' => 'John Doe', 'foo' => 20, 'bar' => 30], 588 | ['name' => 'Mark Moe', 'foo' => 25, 'bar' => 40], 589 | ['name' => 'Larry Loe', 'foo' => 40, 'bar' => 50], 590 | ]); 591 | 592 | $result = $this->db->collection('users')->select('name as full_name')->first(); 593 | $this->assertEquals('Jane Doe', $result->full_name); 594 | $this->assertObjectNotHasAttribute('name', $result); 595 | $this->assertObjectNotHasAttribute('foo', $result); 596 | $this->assertObjectNotHasAttribute('bar', $result); 597 | } 598 | 599 | public function testTruncate() 600 | { 601 | $this->db->collection('users')->insert([ 602 | ['name' => 'Jane Doe', 'age' => 20], 603 | ['name' => 'John Doe', 'age' => 21], 604 | ['name' => 'Mark Moe', 'age' => 20], 605 | ]); 606 | 607 | $this->db->collection('users')->truncate(); 608 | $this->assertEquals(0, $this->db->collection('users')->count()); 609 | } 610 | 611 | public function testPush() 612 | { 613 | $this->db->collection('users')->insert([ 614 | ['name' => 'Jane Doe', 'tags' => [], 'messages' => []], 615 | ['name' => 'John Doe', 'tags' => [], 'messages' => []], 616 | ]); 617 | $id = $this->db->collection('users')->where('name', 'John Doe')->first()->_id; 618 | 619 | // Single value 620 | $modified = $this->db->collection('users')->where('_id', $id)->push('tags', 'tag1'); 621 | $this->assertEquals(1, $modified); 622 | $user = $this->db->collection('users')->find($id); 623 | $this->assertInternalType('array', $user->tags); 624 | $this->assertCount(1, $user->tags); 625 | $this->assertEquals('tag1', $user->tags[0]); 626 | 627 | // Another single value 628 | $this->db->collection('users')->where('_id', $id)->push('tags', 'tag2'); 629 | $user = $this->db->collection('users')->find($id); 630 | $this->assertCount(2, $user->tags); 631 | $this->assertEquals('tag2', $user->tags[1]); 632 | 633 | // Add duplicate 634 | $this->db->collection('users')->where('_id', $id)->push('tags', 'tag2'); 635 | $user = $this->db->collection('users')->find($id); 636 | $this->assertCount(3, $user->tags); 637 | 638 | // Add unique 639 | $this->db->collection('users')->where('_id', $id)->push('tags', 'tag1', true); 640 | $user = $this->db->collection('users')->find($id); 641 | $this->assertCount(3, $user->tags); 642 | 643 | // Add object 644 | $message = ['from' => 'Jane', 'body' => 'Hi John']; 645 | $this->db->collection('users')->where('_id', $id)->push('messages', $message); 646 | $user = $this->db->collection('users')->find($id); 647 | $this->assertCount(1, $user->messages); 648 | $this->assertInternalType('object', $user->messages[0]); 649 | $this->assertEquals('Jane', $user->messages[0]->from); 650 | 651 | // Add multiple values 652 | $this->db->collection('users')->where('_id', $id)->push('tags', ['tag3', 'tag4']); 653 | $user = $this->db->collection('users')->find($id); 654 | $this->assertCount(5, $user->tags); 655 | 656 | // Raw 657 | $this->db->collection('users')->where('_id', $id)->push([ 658 | 'tags' => 'tag3', 659 | 'messages' => ['from' => 'Mark', 'body' => 'Hi John'], 660 | ]); 661 | $user = $this->db->collection('users')->find($id); 662 | $this->assertCount(6, $user->tags); 663 | $this->assertCount(2, $user->messages); 664 | 665 | // Check other user is not modified 666 | $user = $this->db->collection('users')->where('name', 'Jane Doe')->first(); 667 | $this->assertEmpty($user->tags); 668 | 669 | // Push on multiple records 670 | $modified = $this->db->collection('users')->push([ 671 | 'tags' => 'all', 672 | ]); 673 | $this->assertEquals(2, $modified); 674 | $user1 = $this->db->collection('users')->where('name', 'Jane Doe')->first(); 675 | $this->assertContains('all', $user1->tags); 676 | $user2 = $this->db->collection('users')->where('name', 'Jane Doe')->first(); 677 | $this->assertContains('all', $user2->tags); 678 | } 679 | 680 | public function testPull() 681 | { 682 | $this->db->collection('users')->insert([ 683 | [ 684 | 'name' => 'Jane Doe', 685 | 'tags' => ['tag1', 'tag5'], 686 | 'messages' => [], 687 | ], 688 | [ 689 | 'name' => 'John Doe', 690 | 'tags' => ['tag1', 'tag2', 'tag3', 'tag4', 'tag5'], 691 | 'messages' => [ 692 | ['from' => 'Jane', 'body' => 'Hi John'], 693 | ['from' => 'Mark', 'body' => 'Hi John'], 694 | ], 695 | ], 696 | ]); 697 | 698 | $id = $this->db->collection('users')->insertGetId([ 699 | 'name' => 'John Doe', 700 | 'tags' => ['tag1', 'tag2', 'tag3', 'tag4'], 701 | 'messages' => [ 702 | ['from' => 'Jane', 'body' => 'Hi John'], 703 | ['from' => 'Mark', 'body' => 'Hi John'], 704 | ], 705 | ]); 706 | 707 | // Pull single values 708 | $this->db->collection('users')->where('_id', $id)->pull('tags', 'tag3'); 709 | $user = $this->db->collection('users')->find($id); 710 | $this->assertInternalType('array', $user->tags); 711 | $this->assertNotContains('tag3', $user->tags); 712 | 713 | // Pull object 714 | $this->db->collection('users')->where('_id', $id)->pull('messages', ['from' => 'Jane', 'body' => 'Hi John']); 715 | $user = $this->db->collection('users')->find($id); 716 | $this->assertCount(1, $user->messages); 717 | 718 | // Pull multiple values 719 | $this->db->collection('users')->where('_id', $id)->pull('tags', ['tag1', 'tag4']); 720 | $user = $this->db->collection('users')->find($id); 721 | $this->assertNotContains('tag1', $user->tags); 722 | $this->assertNotContains('tag4', $user->tags); 723 | 724 | // Pull from all 725 | $this->db->collection('users')->pull('tags', ['tag5']); 726 | $user1 = $this->db->collection('users')->find($id); 727 | $this->assertNotContains('tag5', $user1->tags); 728 | $user2 = $this->db->collection('users')->where('name', 'Jane Doe')->first(); 729 | $this->assertNotContains('tag5', $user2->tags); 730 | 731 | // Raw 732 | $this->db->collection('users')->where('_id', $id)->pull([ 733 | 'tags' => 'tag2', 734 | 'messages' => ['from' => 'Mark', 'body' => 'Hi John'], 735 | ]); 736 | $user = $this->db->collection('users')->find($id); 737 | $this->assertCount(0, $user->tags); 738 | $this->assertCount(0, $user->messages); 739 | } 740 | 741 | public function testGroupBy() 742 | { 743 | $this->db->collection('users')->insert([ 744 | ['name' => 'John Doe', 'age' => 35, 'title' => 'admin'], 745 | ['name' => 'Jane Doe', 'age' => 33, 'title' => 'admin'], 746 | ['name' => 'Harry Hoe', 'age' => 13, 'title' => 'user'], 747 | ['name' => 'Robert Roe', 'age' => 37, 'title' => 'user'], 748 | ['name' => 'Mark Moe', 'age' => 23, 'title' => 'user'], 749 | ['name' => 'Brett Boe', 'age' => 35, 'title' => 'user'], 750 | ['name' => 'Tommy Toe', 'age' => 33, 'title' => 'user'], 751 | ['name' => 'Yvonne Yoe', 'age' => 35, 'title' => 'admin'], 752 | ['name' => 'Error', 'age' => null, 'title' => null], 753 | ]); 754 | 755 | $users = $this->db->collection('users')->select('title')->groupBy('title')->get(); 756 | $this->assertCount(3, $users); 757 | 758 | $users = $this->db->collection('users')->select('age')->groupBy('age')->get(); 759 | $this->assertCount(6, $users); 760 | 761 | $users = $this->db->collection('users')->select('age')->groupBy('age')->skip(1)->get(); 762 | $this->assertCount(5, $users); 763 | 764 | $users = $this->db->collection('users')->select('age')->groupBy('age')->take(2)->get(); 765 | $this->assertCount(2, $users); 766 | 767 | $users = $this->db->collection('users')->select('age')->groupBy('age')->orderBy('age', 'desc')->get(); 768 | $this->assertEquals(37, $users[0]->age); 769 | $this->assertEquals(35, $users[1]->age); 770 | $this->assertEquals(33, $users[2]->age); 771 | 772 | $users = $this->db->collection('users') 773 | ->select('age', 'name') 774 | ->groupBy('age') 775 | ->skip(1) 776 | ->take(2) 777 | ->orderBy('age', 'desc') 778 | ->get(); 779 | $this->assertCount(2, $users); 780 | $this->assertEquals(35, $users[0]->age); 781 | $this->assertEquals(33, $users[1]->age); 782 | 783 | $users = $this->db->collection('users') 784 | ->select('name') 785 | ->groupBy('age') 786 | ->skip(1) 787 | ->take(2) 788 | ->orderBy('age', 'desc') 789 | ->get(); 790 | $this->assertCount(2, $users); 791 | $this->assertNotNull($users[0]->name); 792 | } 793 | } 794 | --------------------------------------------------------------------------------