Skip to content

Commit 29ff22a

Browse files
committed
Implement withAggregate for embedded relations
1 parent e77c16d commit 29ff22a

File tree

2 files changed

+160
-67
lines changed

2 files changed

+160
-67
lines changed

Diff for: src/Eloquent/Builder.php

+5-3
Original file line numberDiff line numberDiff line change
@@ -326,10 +326,12 @@ public function withAggregate($relations, $column, $function = null)
326326
if ($relation instanceof EmbedsOneOrMany) {
327327
switch ($function) {
328328
case 'count':
329-
$this->project([$alias => ['$size' => ['$ifNull' => ['$' . $relation->getQualifiedForeignKeyName(), []]]]]);
329+
$this->project([$alias => ['$size' => ['$ifNull' => ['$' . $name, []]]]]);
330330
break;
331-
case 'exists':
332-
$this->project([$alias => ['$exists' => '$' . $relation->getQualifiedForeignKeyName()]]);
331+
case 'min':
332+
case 'max':
333+
case 'avg':
334+
$this->project([$alias => ['$' . $function => '$' . $name . '.' . $column]]);
333335
break;
334336
default:
335337
throw new InvalidArgumentException(sprintf('Invalid aggregate function "%s"', $function));

Diff for: tests/Eloquent/EloquentWithAggregateTest.php

+155-64
Original file line numberDiff line numberDiff line change
@@ -3,92 +3,156 @@
33
namespace MongoDB\Laravel\Tests\Eloquent;
44

55
use Illuminate\Database\Eloquent\Builder;
6+
use Illuminate\Support\Collection;
67
use Illuminate\Support\Facades\DB;
78
use MongoDB\Laravel\Eloquent\Model;
89
use MongoDB\Laravel\Tests\TestCase;
910

1011
use function count;
12+
use function ksort;
1113

1214
class EloquentWithAggregateTest extends TestCase
1315
{
1416
protected function tearDown(): void
1517
{
16-
EloquentWithCountModel1::truncate();
17-
EloquentWithCountModel2::truncate();
18-
EloquentWithCountModel3::truncate();
19-
EloquentWithCountModel4::truncate();
18+
EloquentWithAggregateModel1::truncate();
19+
EloquentWithAggregateModel2::truncate();
20+
EloquentWithAggregateModel3::truncate();
21+
EloquentWithAggregateModel4::truncate();
2022

2123
parent::tearDown();
2224
}
2325

2426
public function testWithAggregate()
2527
{
26-
EloquentWithCountModel1::create(['id' => 1]);
27-
$one = EloquentWithCountModel1::create(['id' => 2]);
28+
EloquentWithAggregateModel1::create(['id' => 1]);
29+
$one = EloquentWithAggregateModel1::create(['id' => 2]);
2830
$one->twos()->create(['value' => 4]);
2931
$one->twos()->create(['value' => 6]);
3032

31-
$results = EloquentWithCountModel1::withCount('twos')->where('id', 2);
32-
$this->assertSame([
33+
$results = EloquentWithAggregateModel1::withCount('twos')->where('id', 2);
34+
self::assertSameResults([
3335
['id' => 2, 'twos_count' => 2],
34-
], $results->get()->toArray());
36+
], $results->get());
3537

36-
$results = EloquentWithCountModel1::withMax('twos', 'value')->where('id', 2);
37-
$this->assertSame([
38+
$results = EloquentWithAggregateModel1::withMax('twos', 'value')->where('id', 2);
39+
self::assertSameResults([
3840
['id' => 2, 'twos_max' => 6],
39-
], $results->get()->toArray());
41+
], $results->get());
4042

41-
$results = EloquentWithCountModel1::withMin('twos', 'value')->where('id', 2);
42-
$this->assertSame([
43+
$results = EloquentWithAggregateModel1::withMin('twos', 'value')->where('id', 2);
44+
self::assertSameResults([
4345
['id' => 2, 'twos_min' => 4],
44-
], $results->get()->toArray());
46+
], $results->get());
4547

46-
$results = EloquentWithCountModel1::withAvg('twos', 'value')->where('id', 2);
47-
$this->assertSame([
48+
$results = EloquentWithAggregateModel1::withAvg('twos', 'value')->where('id', 2);
49+
self::assertSameResults([
4850
['id' => 2, 'twos_avg' => 5.0],
49-
], $results->get()->toArray());
51+
], $results->get());
52+
}
53+
54+
public function testWithAggregateEmbed()
55+
{
56+
EloquentWithAggregateModel1::create(['id' => 1]);
57+
$one = EloquentWithAggregateModel1::create(['id' => 2]);
58+
$one->embeddeds()->create(['value' => 4]);
59+
$one->embeddeds()->create(['value' => 6]);
60+
61+
$results = EloquentWithAggregateModel1::withCount('embeddeds')->select('id')->where('id', 2);
62+
self::assertSameResults([
63+
['id' => 2, 'embeddeds_count' => 2],
64+
], $results->get());
65+
66+
$results = EloquentWithAggregateModel1::withMax('embeddeds', 'value')->select('id')->where('id', 2);
67+
self::assertSameResults([
68+
['id' => 2, 'embeddeds_max' => 6],
69+
], $results->get());
70+
71+
$results = EloquentWithAggregateModel1::withMin('embeddeds', 'value')->select('id')->where('id', 2);
72+
self::assertSameResults([
73+
['id' => 2, 'embeddeds_min' => 4],
74+
], $results->get());
75+
76+
$results = EloquentWithAggregateModel1::withAvg('embeddeds', 'value')->select('id')->where('id', 2);
77+
self::assertSameResults([
78+
['id' => 2, 'embeddeds_avg' => 5.0],
79+
], $results->get());
5080
}
5181

5282
public function testWithAggregateFiltered()
5383
{
54-
EloquentWithCountModel1::create(['id' => 1]);
55-
$one = EloquentWithCountModel1::create(['id' => 2]);
84+
EloquentWithAggregateModel1::create(['id' => 1]);
85+
$one = EloquentWithAggregateModel1::create(['id' => 2]);
5686
$one->twos()->create(['value' => 4]);
5787
$one->twos()->create(['value' => 6]);
5888
$one->twos()->create(['value' => 8]);
5989
$filter = static function (Builder $query) {
6090
$query->where('value', '<=', 6);
6191
};
6292

63-
$results = EloquentWithCountModel1::withCount(['twos' => $filter])->where('id', 2);
64-
$this->assertSame([
93+
$results = EloquentWithAggregateModel1::withCount(['twos' => $filter])->where('id', 2);
94+
self::assertSameResults([
6595
['id' => 2, 'twos_count' => 2],
66-
], $results->get()->toArray());
96+
], $results->get());
6797

68-
$results = EloquentWithCountModel1::withMax(['twos' => $filter], 'value')->where('id', 2);
69-
$this->assertSame([
98+
$results = EloquentWithAggregateModel1::withMax(['twos' => $filter], 'value')->where('id', 2);
99+
self::assertSameResults([
70100
['id' => 2, 'twos_max' => 6],
71-
], $results->get()->toArray());
101+
], $results->get());
72102

73-
$results = EloquentWithCountModel1::withMin(['twos' => $filter], 'value')->where('id', 2);
74-
$this->assertSame([
103+
$results = EloquentWithAggregateModel1::withMin(['twos' => $filter], 'value')->where('id', 2);
104+
self::assertSameResults([
75105
['id' => 2, 'twos_min' => 4],
76-
], $results->get()->toArray());
106+
], $results->get());
77107

78-
$results = EloquentWithCountModel1::withAvg(['twos' => $filter], 'value')->where('id', 2);
79-
$this->assertSame([
108+
$results = EloquentWithAggregateModel1::withAvg(['twos' => $filter], 'value')->where('id', 2);
109+
self::assertSameResults([
80110
['id' => 2, 'twos_avg' => 5.0],
81-
], $results->get()->toArray());
111+
], $results->get());
112+
}
113+
114+
public function testWithAggregateEmbedFiltered()
115+
{
116+
self::markTestSkipped('EmbedsMany does not support filtering. $filter requires an expression but the Query Builder generates query predicates.');
117+
118+
EloquentWithAggregateModel1::create(['id' => 1]);
119+
$one = EloquentWithAggregateModel1::create(['id' => 2]);
120+
$one->embeddeds()->create(['value' => 4]);
121+
$one->embeddeds()->create(['value' => 6]);
122+
$one->embeddeds()->create(['value' => 8]);
123+
$filter = static function (Builder $query) {
124+
$query->where('value', '<=', 6);
125+
};
126+
127+
$results = EloquentWithAggregateModel1::withCount(['embeddeds' => $filter])->where('id', 2);
128+
self::assertSameResults([
129+
['id' => 2, 'embeddeds_count' => 2],
130+
], $results->get());
131+
132+
$results = EloquentWithAggregateModel1::withMax(['embeddeds' => $filter], 'value')->where('id', 2);
133+
self::assertSameResults([
134+
['id' => 2, 'embeddeds_max' => 6],
135+
], $results->get());
136+
137+
$results = EloquentWithAggregateModel1::withMin(['embeddeds' => $filter], 'value')->where('id', 2);
138+
self::assertSameResults([
139+
['id' => 2, 'embeddeds_min' => 4],
140+
], $results->get());
141+
142+
$results = EloquentWithAggregateModel1::withAvg(['embeddeds' => $filter], 'value')->where('id', 2);
143+
self::assertSameResults([
144+
['id' => 2, 'embeddeds_avg' => 5.0],
145+
], $results->get());
82146
}
83147

84148
public function testWithAggregateMultipleResults()
85149
{
86150
$connection = DB::connection('mongodb');
87151
$ones = [
88-
EloquentWithCountModel1::create(['id' => 1]),
89-
EloquentWithCountModel1::create(['id' => 2]),
90-
EloquentWithCountModel1::create(['id' => 3]),
91-
EloquentWithCountModel1::create(['id' => 4]),
152+
EloquentWithAggregateModel1::create(['id' => 1]),
153+
EloquentWithAggregateModel1::create(['id' => 2]),
154+
EloquentWithAggregateModel1::create(['id' => 3]),
155+
EloquentWithAggregateModel1::create(['id' => 4]),
92156
];
93157

94158
$ones[0]->twos()->create(['value' => 1]);
@@ -101,88 +165,103 @@ public function testWithAggregateMultipleResults()
101165
$connection->enableQueryLog();
102166

103167
// Count
104-
$results = EloquentWithCountModel1::withCount([
168+
$results = EloquentWithAggregateModel1::withCount([
105169
'twos' => function ($query) {
106170
$query->where('value', '>=', 2);
107171
},
108172
]);
109173

110-
$this->assertSame([
174+
self::assertSameResults([
111175
['id' => 1, 'twos_count' => 2],
112176
['id' => 2, 'twos_count' => 0],
113177
['id' => 3, 'twos_count' => 1],
114178
['id' => 4, 'twos_count' => 0],
115-
], $results->get()->toArray());
179+
], $results->get());
116180

117-
$this->assertSame(2, count($connection->getQueryLog()));
181+
self::assertSame(2, count($connection->getQueryLog()));
118182
$connection->flushQueryLog();
119183

120184
// Max
121-
$results = EloquentWithCountModel1::withMax([
185+
$results = EloquentWithAggregateModel1::withMax([
122186
'twos' => function ($query) {
123187
$query->where('value', '>=', 2);
124188
},
125189
], 'value');
126190

127-
$this->assertSame([
191+
self::assertSameResults([
128192
['id' => 1, 'twos_max' => 3],
129193
['id' => 2, 'twos_max' => null],
130194
['id' => 3, 'twos_max' => 2],
131195
['id' => 4, 'twos_max' => null],
132-
], $results->get()->toArray());
196+
], $results->get());
133197

134-
$this->assertSame(2, count($connection->getQueryLog()));
198+
self::assertSame(2, count($connection->getQueryLog()));
135199
$connection->flushQueryLog();
136200

137201
// Min
138-
$results = EloquentWithCountModel1::withMin([
202+
$results = EloquentWithAggregateModel1::withMin([
139203
'twos' => function ($query) {
140204
$query->where('value', '>=', 2);
141205
},
142206
], 'value');
143207

144-
$this->assertSame([
208+
self::assertSameResults([
145209
['id' => 1, 'twos_min' => 2],
146210
['id' => 2, 'twos_min' => null],
147211
['id' => 3, 'twos_min' => 2],
148212
['id' => 4, 'twos_min' => null],
149-
], $results->get()->toArray());
213+
], $results->get());
150214

151-
$this->assertSame(2, count($connection->getQueryLog()));
215+
self::assertSame(2, count($connection->getQueryLog()));
152216
$connection->flushQueryLog();
153217

154218
// Avg
155-
$results = EloquentWithCountModel1::withAvg([
219+
$results = EloquentWithAggregateModel1::withAvg([
156220
'twos' => function ($query) {
157221
$query->where('value', '>=', 2);
158222
},
159223
], 'value');
160224

161-
$this->assertSame([
225+
self::assertSameResults([
162226
['id' => 1, 'twos_avg' => 2.5],
163227
['id' => 2, 'twos_avg' => null],
164228
['id' => 3, 'twos_avg' => 2.0],
165229
['id' => 4, 'twos_avg' => null],
166-
], $results->get()->toArray());
230+
], $results->get());
167231

168-
$this->assertSame(2, count($connection->getQueryLog()));
232+
self::assertSame(2, count($connection->getQueryLog()));
169233
$connection->flushQueryLog();
170234
}
171235

172236
public function testGlobalScopes()
173237
{
174-
$one = EloquentWithCountModel1::create();
238+
$one = EloquentWithAggregateModel1::create();
175239
$one->fours()->create();
176240

177-
$result = EloquentWithCountModel1::withCount('fours')->first();
178-
$this->assertSame(0, $result->fours_count);
241+
$result = EloquentWithAggregateModel1::withCount('fours')->first();
242+
self::assertSame(0, $result->fours_count);
243+
244+
$result = EloquentWithAggregateModel1::withCount('allFours')->first();
245+
self::assertSame(1, $result->all_fours_count);
246+
}
247+
248+
private static function assertSameResults(array $expected, Collection $collection)
249+
{
250+
$actual = $collection->toArray();
251+
252+
foreach ($actual as &$item) {
253+
ksort($item);
254+
}
255+
256+
foreach ($expected as &$item) {
257+
ksort($item);
258+
}
179259

180-
$result = EloquentWithCountModel1::withCount('allFours')->first();
181-
$this->assertSame(1, $result->all_fours_count);
260+
self::assertSame($expected, $actual);
182261
}
183262
}
184263

185-
class EloquentWithCountModel1 extends Model
264+
class EloquentWithAggregateModel1 extends Model
186265
{
187266
protected $connection = 'mongodb';
188267
public $table = 'one';
@@ -191,21 +270,26 @@ class EloquentWithCountModel1 extends Model
191270

192271
public function twos()
193272
{
194-
return $this->hasMany(EloquentWithCountModel2::class, 'one_id');
273+
return $this->hasMany(EloquentWithAggregateModel2::class, 'one_id');
195274
}
196275

197276
public function fours()
198277
{
199-
return $this->hasMany(EloquentWithCountModel4::class, 'one_id');
278+
return $this->hasMany(EloquentWithAggregateModel4::class, 'one_id');
200279
}
201280

202281
public function allFours()
203282
{
204283
return $this->fours()->withoutGlobalScopes();
205284
}
285+
286+
public function embeddeds()
287+
{
288+
return $this->embedsMany(EloquentWithAggregateEmbeddedModel::class);
289+
}
206290
}
207291

208-
class EloquentWithCountModel2 extends Model
292+
class EloquentWithAggregateModel2 extends Model
209293
{
210294
protected $connection = 'mongodb';
211295
public $table = 'two';
@@ -224,11 +308,11 @@ protected static function boot()
224308

225309
public function threes()
226310
{
227-
return $this->hasMany(EloquentWithCountModel3::class, 'two_id');
311+
return $this->hasMany(EloquentWithAggregateModel3::class, 'two_id');
228312
}
229313
}
230314

231-
class EloquentWithCountModel3 extends Model
315+
class EloquentWithAggregateModel3 extends Model
232316
{
233317
protected $connection = 'mongodb';
234318
public $table = 'three';
@@ -245,7 +329,7 @@ protected static function boot()
245329
}
246330
}
247331

248-
class EloquentWithCountModel4 extends Model
332+
class EloquentWithAggregateModel4 extends Model
249333
{
250334
protected $connection = 'mongodb';
251335
public $table = 'four';
@@ -261,3 +345,10 @@ protected static function boot()
261345
});
262346
}
263347
}
348+
349+
class EloquentWithAggregateEmbeddedModel extends Model
350+
{
351+
protected $connection = 'mongodb';
352+
public $timestamps = false;
353+
protected $guarded = [];
354+
}

0 commit comments

Comments
 (0)