Skip to content

Commit

Permalink
Make it possible to run the aggregation pipeline
Browse files Browse the repository at this point in the history
  • Loading branch information
GromNaN committed Feb 27, 2024
1 parent a2859a6 commit ae70ec5
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 12 deletions.
4 changes: 2 additions & 2 deletions src/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class Builder extends BaseBuilder
/**
* The database collection.
*
* @var \MongoDB\Collection
* @var \MongoDB\Laravel\Collection
*/
protected $collection;

Expand Down Expand Up @@ -569,7 +569,7 @@ public function generateCacheKey()
public function aggregate($function = null, $columns = [])
{
if ($function === null) {
return new PipelineBuilder($this->getPipeline());
return new PipelineBuilder($this->getPipeline(), $this->collection, $this->options);
}

$this->aggregate = [
Expand Down
27 changes: 24 additions & 3 deletions src/Query/PipelineBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,33 @@

namespace MongoDB\Laravel\Query;

use MongoDB\Builder\BuilderEncoder;
use MongoDB\Builder\Stage\FluentFactory;
use MongoDB\Laravel\Collection;

class PipelineBuilder extends FluentFactory
use function array_replace;
use function collect;

final class PipelineBuilder extends FluentFactory
{
public function __construct(array $pipeline = [])
{
public function __construct(
array $pipeline,
private Collection $collection,
private array $options,
) {
$this->pipeline = $pipeline;
}

public function get()
{
$encoder = new BuilderEncoder();
$pipeline = $encoder->encode($this->getPipeline());

$options = array_replace(
['typeMap' => ['root' => 'array', 'document' => 'array']],
$this->options,
);

return collect($this->collection->aggregate($pipeline, $options)->toArray());
}
}
65 changes: 58 additions & 7 deletions tests/Query/PipelineBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@

namespace MongoDB\Laravel\Tests\Query;

use DateTimeImmutable;
use Illuminate\Support\Collection;
use MongoDB\BSON\Document;
use MongoDB\BSON\ObjectId;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Builder\BuilderEncoder;
use MongoDB\Builder\Expression;
use MongoDB\Builder\Type\TimeUnit;
use MongoDB\Builder\Variable;
use MongoDB\Laravel\Query\PipelineBuilder;
use MongoDB\Laravel\Tests\Models\User;
use MongoDB\Laravel\Tests\TestCase;
Expand All @@ -14,27 +21,71 @@

class PipelineBuilderTest extends TestCase
{
public function tearDown(): void
{
User::truncate();
}

public function testCreateFromQueryBuilder(): void
{
$builder = User::where('foo', 'bar')->aggregate();
assert($builder instanceof PipelineBuilder);
$builder->addFields(baz: 'qux');
User::insert([
// @todo apply cast to mass insert?
['name' => 'John Doe', 'birthday' => new UTCDateTime(new DateTimeImmutable('1989-01-01'))],
['name' => 'Jane Doe', 'birthday' => new UTCDateTime(new DateTimeImmutable('1990-01-01'))],
]);

// Create the aggregation pipeline from the query builder
$pipeline = User::where('name', 'John Doe')->aggregate();
assert($pipeline instanceof PipelineBuilder);
$pipeline
->addFields(
age: Expression::dateDiff(
startDate: Expression::dateFieldPath('birthday'),
endDate: Variable::now(),
unit: TimeUnit::Year,
),
)
->unset('birthday');

// The encoder is used to convert the pipeline to a BSON document
$codec = new BuilderEncoder();
$pipeline = $codec->encode($builder->getPipeline());
$json = Document::fromPHP(['pipeline' => $pipeline])->toCanonicalExtendedJSON();
$json = Document::fromPHP([
'pipeline' => $codec->encode($pipeline->getPipeline()),
])->toCanonicalExtendedJSON();

// Compare with the expected pipeline
$expected = Document::fromPHP([
'pipeline' => [
[
'$match' => ['foo' => 'bar'],
'$match' => ['name' => 'John Doe'],
],
[
'$addFields' => ['baz' => 'qux'],
'$addFields' => [
'age' => [
'$dateDiff' => [
'startDate' => '$birthday',
'endDate' => '$$NOW',
'unit' => 'year',
],
],
],
],
[
'$unset' => ['birthday'],
],
],
])->toCanonicalExtendedJSON();

$this->assertJsonStringEqualsJsonString($expected, $json);

// Execute the pipeline and verify the results
$results = $pipeline->get();

$this->assertInstanceOf(Collection::class, $results);
$this->assertCount(1, $results);
$this->assertInstanceOf(ObjectId::class, $results->first()['_id']);
$this->assertSame('John Doe', $results->first()['name']);
$this->assertIsInt($results->first()['age']);
$this->assertArrayNotHasKey('birthday', $results->first());
}
}

0 comments on commit ae70ec5

Please sign in to comment.