Skip to content

Commit 3692165

Browse files
committed
PHPORM-286 Add Query::countByGroup and other aggregateByGroup functions
1 parent d6d8004 commit 3692165

File tree

2 files changed

+65
-2
lines changed

2 files changed

+65
-2
lines changed

src/Query/Builder.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use Override;
3131
use RuntimeException;
3232
use stdClass;
33+
use TypeError;
3334

3435
use function array_fill_keys;
3536
use function array_filter;
@@ -324,8 +325,10 @@ public function toMql(): array
324325
// this mimics SQL's behaviour a bit.
325326
$group[$column] = ['$last' => '$' . $column];
326327
}
328+
}
327329

328-
// Do the same for other columns that are selected.
330+
// Add the last value of each column when there is no aggregate function.
331+
if ($this->groups && ! $this->aggregate) {
329332
foreach ($columns as $column) {
330333
$key = str_replace('.', '_', $column);
331334

@@ -349,7 +352,7 @@ public function toMql(): array
349352

350353
$aggregations = blank($this->aggregate['columns']) ? [] : $this->aggregate['columns'];
351354

352-
if (in_array('*', $aggregations) && $function === 'count') {
355+
if (in_array('*', $aggregations) && $function === 'count' && empty($group['_id'])) {
353356
$options = $this->inheritConnectionOptions($this->options);
354357

355358
return ['countDocuments' => [$wheres, $options]];
@@ -559,6 +562,8 @@ public function generateCacheKey()
559562
/** @return ($function is null ? AggregationBuilder : mixed) */
560563
public function aggregate($function = null, $columns = ['*'])
561564
{
565+
assert(is_array($columns), new TypeError(sprintf('Argument #2 ($columns) must be of type array, %s given', get_debug_type($columns))));
566+
562567
if ($function === null) {
563568
if (! trait_exists(FluentFactoryTrait::class)) {
564569
// This error will be unreachable when the mongodb/builder package will be merged into mongodb/mongodb
@@ -599,13 +604,32 @@ public function aggregate($function = null, $columns = ['*'])
599604
$this->columns = $previousColumns;
600605
$this->bindings['select'] = $previousSelectBindings;
601606

607+
// When the aggregation is per group, we return the results as is.
608+
if ($this->groups) {
609+
return $results->map(function (object $result) {
610+
unset($result->id);
611+
612+
return $result;
613+
});
614+
}
615+
602616
if (isset($results[0])) {
603617
$result = (array) $results[0];
604618

605619
return $result['aggregate'];
606620
}
607621
}
608622

623+
/**
624+
* {@inheritDoc}
625+
*
626+
* @see \Illuminate\Database\Query\Builder::aggregateByGroup()
627+
*/
628+
public function aggregateByGroup(string $function, array $columns = ['*'])
629+
{
630+
return $this->aggregate($function, $columns);
631+
}
632+
609633
/** @inheritdoc */
610634
public function exists()
611635
{

tests/QueryBuilderTest.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Carbon\Carbon;
88
use DateTime;
99
use DateTimeImmutable;
10+
use Illuminate\Support\Collection as LaravelCollection;
1011
use Illuminate\Support\Facades\Date;
1112
use Illuminate\Support\Facades\DB;
1213
use Illuminate\Support\LazyCollection;
@@ -32,6 +33,7 @@
3233
use function count;
3334
use function key;
3435
use function md5;
36+
use function method_exists;
3537
use function sort;
3638
use function strlen;
3739

@@ -617,6 +619,43 @@ public function testSubdocumentArrayAggregate()
617619
$this->assertEquals(12, DB::table('items')->avg('amount.*.hidden'));
618620
}
619621

622+
public function testAggregateGroupBy()
623+
{
624+
DB::table('users')->insert([
625+
['name' => 'John Doe', 'role' => 'admin', 'score' => 1],
626+
['name' => 'Jane Doe', 'role' => 'admin', 'score' => 2],
627+
['name' => 'Robert Roe', 'role' => 'user', 'score' => 4],
628+
]);
629+
630+
$results = DB::table('users')->groupBy('role')->orderBy('role')->aggregateByGroup('count');
631+
$this->assertInstanceOf(LaravelCollection::class, $results);
632+
$this->assertEquals([(object) ['role' => 'admin', 'aggregate' => 2], (object) ['role' => 'user', 'aggregate' => 1]], $results->toArray());
633+
634+
if (! method_exists(Builder::class, 'countByGroup')) {
635+
$this->markTestSkipped('countBy* function require Laravel v11.38+');
636+
}
637+
638+
$results = DB::table('users')->groupBy('role')->orderBy('role')->countByGroup();
639+
$this->assertInstanceOf(LaravelCollection::class, $results);
640+
$this->assertEquals([(object) ['role' => 'admin', 'aggregate' => 2], (object) ['role' => 'user', 'aggregate' => 1]], $results->toArray());
641+
642+
$results = DB::table('users')->groupBy('role')->orderBy('role')->maxByGroup('score');
643+
$this->assertInstanceOf(LaravelCollection::class, $results);
644+
$this->assertEquals([(object) ['role' => 'admin', 'aggregate' => 2], (object) ['role' => 'user', 'aggregate' => 4]], $results->toArray());
645+
646+
$results = DB::table('users')->groupBy('role')->orderBy('role')->minByGroup('score');
647+
$this->assertInstanceOf(LaravelCollection::class, $results);
648+
$this->assertEquals([(object) ['role' => 'admin', 'aggregate' => 1], (object) ['role' => 'user', 'aggregate' => 4]], $results->toArray());
649+
650+
$results = DB::table('users')->groupBy('role')->orderBy('role')->sumByGroup('score');
651+
$this->assertInstanceOf(LaravelCollection::class, $results);
652+
$this->assertEquals([(object) ['role' => 'admin', 'aggregate' => 3], (object) ['role' => 'user', 'aggregate' => 4]], $results->toArray());
653+
654+
$results = DB::table('users')->groupBy('role')->orderBy('role')->avgByGroup('score');
655+
$this->assertInstanceOf(LaravelCollection::class, $results);
656+
$this->assertEquals([(object) ['role' => 'admin', 'aggregate' => 1.5], (object) ['role' => 'user', 'aggregate' => 4]], $results->toArray());
657+
}
658+
620659
public function testUpdateWithUpsert()
621660
{
622661
DB::table('items')->where('name', 'knife')

0 commit comments

Comments
 (0)