Commit 81ae5a63 authored by Fiete Börner's avatar Fiete Börner Committed by Jens Segers

Aggregate subdocument arrays (#918)

* add sub document aggregation array functionality

example:
    Model::sum('subarray.*.price');

this method is much simpler as to use as complex raw aggregations

for this function a $unwind directive will be pushed in pipeline before $group

* change testSubdocumentArrayAggregate

change test for different scenarios

* rebase to latest master and fix style ci issues
parent 333dd573
...@@ -208,6 +208,7 @@ class Builder extends BaseBuilder ...@@ -208,6 +208,7 @@ class Builder extends BaseBuilder
// Use MongoDB's aggregation framework when using grouping or aggregation functions. // Use MongoDB's aggregation framework when using grouping or aggregation functions.
if ($this->groups or $this->aggregate or $this->paginating) { if ($this->groups or $this->aggregate or $this->paginating) {
$group = []; $group = [];
$unwinds = [];
// Add grouping columns to the $group part of the aggregation pipeline. // Add grouping columns to the $group part of the aggregation pipeline.
if ($this->groups) { if ($this->groups) {
...@@ -233,6 +234,13 @@ class Builder extends BaseBuilder ...@@ -233,6 +234,13 @@ class Builder extends BaseBuilder
$function = $this->aggregate['function']; $function = $this->aggregate['function'];
foreach ($this->aggregate['columns'] as $column) { foreach ($this->aggregate['columns'] as $column) {
// Add unwind if a subdocument array should be aggregated
// column: subarray.price => {$unwind: '$subarray'}
if (count($splitColumns = explode('.*.', $column)) == 2) {
$unwinds[] = $splitColumns[0];
$column = implode('.', $splitColumns);
}
// Translate count into sum. // Translate count into sum.
if ($function == 'count') { if ($function == 'count') {
$group['aggregate'] = ['$sum' => 1]; $group['aggregate'] = ['$sum' => 1];
...@@ -262,6 +270,12 @@ class Builder extends BaseBuilder ...@@ -262,6 +270,12 @@ class Builder extends BaseBuilder
if ($wheres) { if ($wheres) {
$pipeline[] = ['$match' => $wheres]; $pipeline[] = ['$match' => $wheres];
} }
// apply unwinds for subdocument array aggregation
foreach ($unwinds as $unwind) {
$pipeline[] = ['$unwind' => '$' . $unwind];
}
if ($group) { if ($group) {
$pipeline[] = ['$group' => $group]; $pipeline[] = ['$group' => $group];
} }
......
...@@ -419,6 +419,22 @@ class QueryBuilderTest extends TestCase ...@@ -419,6 +419,22 @@ class QueryBuilderTest extends TestCase
$this->assertEquals(16.25, DB::collection('items')->avg('amount.hidden')); $this->assertEquals(16.25, DB::collection('items')->avg('amount.hidden'));
} }
public function testSubdocumentArrayAggregate()
{
DB::collection('items')->insert([
['name' => 'knife', 'amount' => [['hidden' => 10, 'found' => 3],['hidden' => 5, 'found' => 2]]],
['name' => 'fork', 'amount' => [['hidden' => 35, 'found' => 12],['hidden' => 7, 'found' => 17],['hidden' => 1, 'found' => 19]]],
['name' => 'spoon', 'amount' => [['hidden' => 14, 'found' => 21]]],
['name' => 'teaspoon', 'amount' => []],
]);
$this->assertEquals(72, DB::collection('items')->sum('amount.*.hidden'));
$this->assertEquals(6, DB::collection('items')->count('amount.*.hidden'));
$this->assertEquals(1, DB::collection('items')->min('amount.*.hidden'));
$this->assertEquals(35, DB::collection('items')->max('amount.*.hidden'));
$this->assertEquals(12, DB::collection('items')->avg('amount.*.hidden'));
}
public function testUpsert() public function testUpsert()
{ {
DB::collection('items')->where('name', 'knife') DB::collection('items')->where('name', 'knife')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment