mongodb / laravel-mongodb

A MongoDB based Eloquent model and Query builder for Laravel (Moloquent)
https://www.mongodb.com/compatibility/mongodb-laravel-integration
MIT License
6.99k stars 1.42k forks source link

paginate() method thows an exception when query contains 'near' #3063

Open gw-patrick opened 1 month ago

gw-patrick commented 1 month ago

Description:

Running a query to find a record near a given position results in an error - MongoDB\Driver\Exception\CommandException $geoNear, $near, and $nearSphere are not allowed in this context..

Steps to reproduce

  1. Create a collection named 'stores' using the json below -

    [{
    "_id": {
    "$oid": "652d4cdaf8960f9f37c589e7"
    },
    "position": [
    9.224596977233887,
    52.03082275390625
    ],
    "name": "Store A",
    "created_at": {
    "$date": "2023-10-16T14:46:50.364Z"
    },
    "updated_at": {
    "$date": "2023-10-16T14:46:50.364Z"
    }
    },
    {
    "_id": {
    "$oid": "652d4cdaf8960f9f37c589e9"
    },
    "position": [
    9.224596977233887,
    52.03082275390625
    ],
    "name": "Store B",
    "created_at": {
    "$date": "2023-10-16T14:46:50.365Z"
    },
    "updated_at": {
    "$date": "2023-10-16T14:46:50.365Z"
    }
    },
    {
    "_id": {
    "$oid": "652d4cdef8960f9f37c589f0"
    },
    "position": [
    9.224596977233887,
    52.03082275390625
    ],
    "name": "Store C",
    "created_at": {
    "$date": "2023-10-16T14:46:54.670Z"
    },
    "updated_at": {
    "$date": "2023-10-16T14:46:54.670Z"
    }
    }]
  2. Run the query below (on laravel tinker) to get a paginated list of stores near a location

    $query = DB::connection('mongo')
    ->collection('stores')
    ->where('position', 'near', [
        '$geometry' => [
            'type' => 'Point',
            'coordinates' => [ 
              9.3731451034546,
              52.1019308 
            ],
        ],
        '$maxDistance' => 50,
    ]);
    $query->paginate(); // this results in error
    // $query->get(); // this works

Expected behaviour

Get a paginated list of records.

Actual behaviour

An exception is thrown: - MongoDB\Driver\Exception\CommandException $geoNear, $near, and $nearSphere are not allowed in this context.

Stacktrace: MongoDB\Driver\Exception\CommandException $geoNear, $near, and $nearSphere are not allowed in this context. -- () at vendor/mongodb/mongodb/src/Operation/Aggregate.php:347 MongoDB\Driver\Server->executeReadCommand() at vendor/mongodb/mongodb/src/Operation/Aggregate.php:347 MongoDB\Operation\Aggregate->executeCommand() at vendor/mongodb/mongodb/src/Operation/Aggregate.php:254 MongoDB\Operation\Aggregate->execute() at vendor/mongodb/mongodb/src/Operation/CountDocuments.php:125 MongoDB\Operation\CountDocuments->execute() at vendor/mongodb/mongodb/src/Collection.php:307 MongoDB\Collection->countDocuments() at vendor/mongodb/laravel-mongodb/src/Collection.php:49 MongoDB\Laravel\Collection->__call() at vendor/mongodb/laravel-mongodb/src/Query/Builder.php:462 MongoDB\Laravel\Query\Builder->getFresh() at vendor/mongodb/laravel-mongodb/src/Query/Builder.php:239 MongoDB\Laravel\Query\Builder->get() at vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php:2904 Illuminate\Database\Query\Builder->runPaginationCountQuery() at vendor/mongodb/laravel-mongodb/src/Query/Builder.php:932 MongoDB\Laravel\Query\Builder->runPaginationCountQuery() at vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php:2863 Illuminate\Database\Query\Builder->getCountForPagination() at vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php:922 Illuminate\Database\Eloquent\Builder->paginate() at eval()'d code:1 eval() at vendor/psy/psysh/src/ExecutionLoopClosure.php:52 Psy\{closure}() at vendor/psy/psysh/src/ExecutionClosure.php:89 Psy\ExecutionClosure->execute() at vendor/psy/psysh/src/Shell.php:383 Psy\Shell->doInteractiveRun() at vendor/psy/psysh/src/Shell.php:354 Psy\Shell->doRun() at vendor/symfony/console/Application.php:175 Symfony\Component\Console\Application->run() at vendor/psy/psysh/src/Shell.php:329 Psy\Shell->run() at vendor/laravel/tinker/src/Console/TinkerCommand.php:85 Laravel\Tinker\Console\TinkerCommand->handle() at vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:36 Illuminate\Container\BoundMethod::Illuminate\Container\{closure}() at vendor/laravel/framework/src/Illuminate/Container/Util.php:41 Illuminate\Container\Util::unwrapIfClosure() at vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:93 Illuminate\Container\BoundMethod::callBoundMethod() at vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:35 Illuminate\Container\BoundMethod::call() at vendor/laravel/framework/src/Illuminate/Container/Container.php:662 Illuminate\Container\Container->call() at vendor/laravel/framework/src/Illuminate/Console/Command.php:211 Illuminate\Console\Command->execute() at vendor/symfony/console/Command/Command.php:326 Symfony\Component\Console\Command\Command->run() at vendor/laravel/framework/src/Illuminate/Console/Command.php:180 Illuminate\Console\Command->run() at vendor/symfony/console/Application.php:1096 Symfony\Component\Console\Application->doRunCommand() at vendor/symfony/console/Application.php:324 Symfony\Component\Console\Application->doRun() at vendor/symfony/console/Application.php:175 Symfony\Component\Console\Application->run() at vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php:201 Illuminate\Foundation\Console\Kernel->handle() at artisan:35
bisht2050 commented 1 month ago

Tracked with PHPORM-221.

GromNaN commented 1 month ago

This is due to the restriction on query operators of the countDocuments method.

Since this method uses an aggregation pipeline, some query operators accepted within a MongoDB\Collection::count() filter cannot be used.

It works if you update your query with something like this:

$query = User::where('position', 'geoWithin', [
    '$centerSphere' => [
        [9.3731451034546, 52.1019308], // center of the circle
        50 / 6378100, // radius in radians, 50 meters divided by the Earth's radius in meters
    ],
]);

The only difference is that this is not sorted by distance.