Skip to content
This repository was archived by the owner on Aug 22, 2023. It is now read-only.

Commit ec40454

Browse files
committed
Add Query::toMql() and tests on query syntax
1 parent b6e8a44 commit ec40454

File tree

3 files changed

+144
-66
lines changed

3 files changed

+144
-66
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"illuminate/container": "^10.0",
2424
"illuminate/database": "^10.0",
2525
"illuminate/events": "^10.0",
26-
"mongodb/mongodb": "^1.15"
26+
"mongodb/mongodb": "^1.15",
27+
"ext-mongodb": "*"
2728
},
2829
"require-dev": {
2930
"phpunit/phpunit": "^9.5.10",

src/Query/Builder.php

Lines changed: 57 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use MongoDB\BSON\ObjectID;
1616
use MongoDB\BSON\Regex;
1717
use MongoDB\BSON\UTCDateTime;
18+
use MongoDB\Driver\Cursor;
1819
use RuntimeException;
1920

2021
/**
@@ -215,27 +216,25 @@ public function cursor($columns = [])
215216
}
216217

217218
/**
218-
* Execute the query as a fresh "select" statement.
219+
* Return the Mongo Query to be run in the form of a 1 element array like ['method' => [arguments]].
219220
*
220-
* @param array $columns
221-
* @param bool $returnLazy
222-
* @return array|static[]|Collection|LazyCollection
221+
* @param $columns
222+
* @return array<string, mixed[]>
223223
*/
224-
public function getFresh($columns = [], $returnLazy = false)
224+
public function toMql($columns = []): array
225225
{
226226
// If no columns have been specified for the select statement, we will set them
227227
// here to either the passed columns, or the standard default of retrieving
228228
// all of the columns on the table using the "wildcard" column character.
229-
if ($this->columns === null) {
230-
$this->columns = $columns;
229+
if ($this->columns !== null) {
230+
$columns = $this->columns;
231231
}
232232

233233
// Drop all columns if * is present, MongoDB does not work this way.
234-
if (in_array('*', $this->columns)) {
235-
$this->columns = [];
234+
if (in_array('*', $columns)) {
235+
$columns = [];
236236
}
237237

238-
// Compile wheres
239238
$wheres = $this->compileWheres();
240239

241240
// Use MongoDB's aggregation framework when using grouping or aggregation functions.
@@ -254,7 +253,7 @@ public function getFresh($columns = [], $returnLazy = false)
254253
}
255254

256255
// Do the same for other columns that are selected.
257-
foreach ($this->columns as $column) {
256+
foreach ($columns as $column) {
258257
$key = str_replace('.', '_', $column);
259258

260259
$group[$key] = ['$last' => '$'.$column];
@@ -279,21 +278,14 @@ public function getFresh($columns = [], $returnLazy = false)
279278
$aggregations = blank($this->aggregate['columns']) ? [] : $this->aggregate['columns'];
280279

281280
if (in_array('*', $aggregations) && $function == 'count') {
282-
// When ORM is paginating, count doesnt need a aggregation, just a cursor operation
281+
// When ORM is paginating, count doesn't need an aggregation, just a cursor operation
283282
// elseif added to use this only in pagination
284283
// https://docs.mongodb.com/manual/reference/method/cursor.count/
285284
// count method returns int
286285

287-
$totalResults = $this->collection->count($wheres);
288-
// Preserving format expected by framework
289-
$results = [
290-
[
291-
'_id' => null,
292-
'aggregate' => $totalResults,
293-
],
286+
return [
287+
'count' => [$wheres, []],
294288
];
295-
296-
return new Collection($results);
297289
} elseif ($function == 'count') {
298290
// Translate count into sum.
299291
$group['aggregate'] = ['$sum' => 1];
@@ -348,34 +340,23 @@ public function getFresh($columns = [], $returnLazy = false)
348340

349341
$options = $this->inheritConnectionOptions($options);
350342

351-
// Execute aggregation
352-
$results = iterator_to_array($this->collection->aggregate($pipeline, $options));
353-
354-
// Return results
355-
return new Collection($results);
343+
return ['aggregate' => [$pipeline, $options]];
356344
} // Distinct query
357345
elseif ($this->distinct) {
358346
// Return distinct results directly
359-
$column = isset($this->columns[0]) ? $this->columns[0] : '_id';
347+
$column = isset($columns[0]) ? $columns[0] : '_id';
360348

361349
$options = $this->inheritConnectionOptions();
362350

363-
// Execute distinct
364-
$result = $this->collection->distinct($column, $wheres ?: [], $options);
365-
366-
return new Collection($result);
351+
return ['distinct' => [$column, $wheres ?: [], $options]];
367352
} // Normal query
368353
else {
369-
$columns = [];
370-
371354
// Convert select columns to simple projections.
372-
foreach ($this->columns as $column) {
373-
$columns[$column] = true;
374-
}
355+
$projection = array_fill_keys($columns, true);
375356

376357
// Add custom projections.
377358
if ($this->projections) {
378-
$columns = array_merge($columns, $this->projections);
359+
$projection = array_merge($projection, $this->projections);
379360
}
380361
$options = [];
381362

@@ -395,8 +376,8 @@ public function getFresh($columns = [], $returnLazy = false)
395376
if ($this->hint) {
396377
$options['hint'] = $this->hint;
397378
}
398-
if ($columns) {
399-
$options['projection'] = $columns;
379+
if ($projection) {
380+
$options['projection'] = $projection;
400381
}
401382

402383
// Fix for legacy support, converts the results to arrays instead of objects.
@@ -409,22 +390,46 @@ public function getFresh($columns = [], $returnLazy = false)
409390

410391
$options = $this->inheritConnectionOptions($options);
411392

412-
// Execute query and get MongoCursor
413-
$cursor = $this->collection->find($wheres, $options);
393+
return ['find' => [$wheres, $options]];
394+
}
395+
}
414396

415-
if ($returnLazy) {
416-
return LazyCollection::make(function () use ($cursor) {
417-
foreach ($cursor as $item) {
418-
yield $item;
419-
}
420-
});
421-
}
397+
/**
398+
* Execute the query as a fresh "select" statement.
399+
*
400+
* @param array $columns
401+
* @param bool $returnLazy
402+
* @return array|static[]|Collection|LazyCollection
403+
*/
404+
public function getFresh($columns = [], $returnLazy = false)
405+
{
406+
$command = $this->toMql($columns);
422407

423-
// Return results as an array with numeric keys
424-
$results = iterator_to_array($cursor, false);
408+
$result = call_user_func_array([$this->collection, key($command)], current($command));
425409

426-
return new Collection($results);
410+
// Wrap "count" results in an array
411+
if (is_int($result)) {
412+
$result = [
413+
[
414+
'_id' => null,
415+
'aggregate' => $result,
416+
],
417+
];
418+
}
419+
420+
if ($returnLazy) {
421+
return LazyCollection::make(function () use ($result) {
422+
foreach ($result as $item) {
423+
yield $item;
424+
}
425+
});
426+
}
427+
428+
if ($result instanceof \Traversable) {
429+
$result = iterator_to_array($result);
427430
}
431+
432+
return new Collection($result);
428433
}
429434

430435
/**
@@ -871,7 +876,7 @@ protected function performUpdate($query, array $options = [])
871876
$options = $this->inheritConnectionOptions($options);
872877

873878
$wheres = $this->compileWheres();
874-
$result = $this->collection->UpdateMany($wheres, $query, $options);
879+
$result = $this->collection->updateMany($wheres, $query, $options);
875880
if (1 == (int) $result->isAcknowledged()) {
876881
return $result->getModifiedCount() ? $result->getModifiedCount() : $result->getUpsertedCount();
877882
}

tests/QueryBuilderTest.php

Lines changed: 85 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
use DateTimeImmutable;
99
use Illuminate\Support\Facades\Date;
1010
use Illuminate\Support\Facades\DB;
11-
use Illuminate\Support\LazyCollection;
1211
use Illuminate\Testing\Assert;
12+
use Illuminate\Tests\Database\DatabaseQueryBuilderTest;
1313
use Jenssegers\Mongodb\Collection;
14+
use Jenssegers\Mongodb\Connection;
1415
use Jenssegers\Mongodb\Query\Builder;
16+
use Jenssegers\Mongodb\Query\Processor;
1517
use Jenssegers\Mongodb\Tests\Models\Item;
1618
use Jenssegers\Mongodb\Tests\Models\User;
19+
use Mockery as m;
1720
use MongoDB\BSON\ObjectId;
1821
use MongoDB\BSON\Regex;
1922
use MongoDB\BSON\UTCDateTime;
@@ -840,20 +843,89 @@ public function testHintOptions()
840843
$this->assertEquals('fork', $results[0]['name']);
841844
}
842845

843-
public function testCursor()
846+
public function testOrderBy()
844847
{
845-
$data = [
846-
['name' => 'fork', 'tags' => ['sharp', 'pointy']],
847-
['name' => 'spork', 'tags' => ['sharp', 'pointy', 'round', 'bowl']],
848-
['name' => 'spoon', 'tags' => ['round', 'bowl']],
849-
];
850-
DB::collection('items')->insert($data);
848+
DB::collection('items')->insert([
849+
['name' => 'alpha'],
850+
['name' => 'gamma'],
851+
['name' => 'beta'],
852+
]);
853+
$result = DB::collection('items')->orderBy('name', 'desc')->get();
851854

852-
$results = DB::collection('items')->orderBy('_id', 'asc')->cursor();
855+
$result = $result->map(function ($item) {
856+
return $item['name'];
857+
});
853858

854-
$this->assertInstanceOf(LazyCollection::class, $results);
855-
foreach ($results as $i => $result) {
856-
$this->assertEquals($data[$i]['name'], $result['name']);
857-
}
859+
$this->assertSame(['gamma', 'beta', 'alpha'], $result->toArray());
860+
}
861+
862+
public function testLimitOffset()
863+
{
864+
DB::collection('items')->insert([
865+
['name' => 'alpha'],
866+
['name' => 'gamma'],
867+
['name' => 'beta'],
868+
]);
869+
870+
// Offset only
871+
$result = DB::collection('items')->orderBy('name')->offset(1)->get();
872+
$this->assertSame(['beta', 'gamma'], $result->map(function ($item) { return $item['name']; })->toArray());
873+
874+
// Limit only
875+
$result = DB::collection('items')->orderBy('name')->limit(2)->get();
876+
$this->assertSame(['alpha', 'beta'], $result->map(function ($item) { return $item['name']; })->toArray());
877+
878+
// Limit and offset
879+
$result = DB::collection('items')->orderBy('name')->limit(1)->offset(1)->get();
880+
$this->assertSame(['beta'], $result->map(function ($item) { return $item['name']; })->toArray());
881+
882+
// Empty result
883+
$result = DB::collection('items')->orderBy('name')->offset(5)->get();
884+
$this->assertSame([], $result->toArray());
885+
}
886+
887+
/** @see DatabaseQueryBuilderTest::testLimitsAndOffsets() */
888+
public function testLimitsAndOffsets()
889+
{
890+
$builder = $this->getBuilder();
891+
$builder->select('*')->offset(5)->limit(10);
892+
$this->assertSame(['find' => [[], ['skip' => 5, 'limit' => 10, 'typeMap' => ['root' => 'array', 'document' => 'array']]]], $builder->toMql());
893+
894+
$builder = $this->getBuilder();
895+
$builder->select('*')->limit(10)->limit(null);
896+
$this->assertSame(['find' => [[], ['typeMap' => ['root' => 'array', 'document' => 'array']]]], $builder->toMql());
897+
898+
$builder = $this->getBuilder();
899+
$builder->select('*')->limit(0);
900+
$this->assertSame(['find' => [[], ['typeMap' => ['root' => 'array', 'document' => 'array']]]], $builder->toMql());
901+
902+
$builder = $this->getBuilder();
903+
$builder->select('*')->skip(5)->take(10);
904+
$this->assertSame(['find' => [[], ['skip' => 5, 'limit' => 10, 'typeMap' => ['root' => 'array', 'document' => 'array']]]], $builder->toMql());
905+
906+
$builder = $this->getBuilder();
907+
$builder->select('*')->skip(0)->take(0);
908+
$this->assertSame(['find' => [[], ['typeMap' => ['root' => 'array', 'document' => 'array']]]], $builder->toMql());
909+
910+
$builder = $this->getBuilder();
911+
$builder->select('*')->skip(-5)->take(-10);
912+
$this->assertSame(['find' => [[], ['typeMap' => ['root' => 'array', 'document' => 'array']]]], $builder->toMql());
913+
914+
$builder = $this->getBuilder();
915+
$builder->select('*')->skip(null)->take(null);
916+
$this->assertSame(['find' => [[], ['typeMap' => ['root' => 'array', 'document' => 'array']]]], $builder->toMql());
917+
918+
$builder = $this->getBuilder();
919+
$builder->select('*')->skip(5)->take(null);
920+
$this->assertSame(['find' => [[], ['skip' => 5, 'typeMap' => ['root' => 'array', 'document' => 'array']]]], $builder->toMql());
921+
}
922+
923+
protected function getBuilder()
924+
{
925+
$connection = m::mock(Connection::class);
926+
$processor = m::mock(Processor::class);
927+
$connection->shouldReceive('getSession')->andReturn(null);
928+
929+
return new Builder($connection, $processor);
858930
}
859931
}

0 commit comments

Comments
 (0)