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

Commit f7fe1bf

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

File tree

3 files changed

+143
-67
lines changed

3 files changed

+143
-67
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: 56 additions & 53 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,12 @@ 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-
],
294-
];
295-
296-
return new Collection($results);
286+
return ['count' => [$wheres, []]];
297287
} elseif ($function == 'count') {
298288
// Translate count into sum.
299289
$group['aggregate'] = ['$sum' => 1];
@@ -348,34 +338,23 @@ public function getFresh($columns = [], $returnLazy = false)
348338

349339
$options = $this->inheritConnectionOptions($options);
350340

351-
// Execute aggregation
352-
$results = iterator_to_array($this->collection->aggregate($pipeline, $options));
353-
354-
// Return results
355-
return new Collection($results);
341+
return ['aggregate' => [$pipeline, $options]];
356342
} // Distinct query
357343
elseif ($this->distinct) {
358344
// Return distinct results directly
359-
$column = isset($this->columns[0]) ? $this->columns[0] : '_id';
345+
$column = isset($columns[0]) ? $columns[0] : '_id';
360346

361347
$options = $this->inheritConnectionOptions();
362348

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

376355
// Add custom projections.
377356
if ($this->projections) {
378-
$columns = array_merge($columns, $this->projections);
357+
$projection = array_merge($projection, $this->projections);
379358
}
380359
$options = [];
381360

@@ -395,8 +374,8 @@ public function getFresh($columns = [], $returnLazy = false)
395374
if ($this->hint) {
396375
$options['hint'] = $this->hint;
397376
}
398-
if ($columns) {
399-
$options['projection'] = $columns;
377+
if ($projection) {
378+
$options['projection'] = $projection;
400379
}
401380

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

410389
$options = $this->inheritConnectionOptions($options);
411390

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

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

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

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

430433
/**
@@ -871,7 +874,7 @@ protected function performUpdate($query, array $options = [])
871874
$options = $this->inheritConnectionOptions($options);
872875

873876
$wheres = $this->compileWheres();
874-
$result = $this->collection->UpdateMany($wheres, $query, $options);
877+
$result = $this->collection->updateMany($wheres, $query, $options);
875878
if (1 == (int) $result->isAcknowledged()) {
876879
return $result->getModifiedCount() ? $result->getModifiedCount() : $result->getUpsertedCount();
877880
}

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)