Skip to content

Commit e2d0595

Browse files
committed
Merge branch '2.0.x'
* 2.0.x: Don't apply filters when getting nested pipelines Use majority write concern to fix flaky test
2 parents c2daef3 + cdae043 commit e2d0595

4 files changed

Lines changed: 116 additions & 11 deletions

File tree

lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@
1616
use MongoDB\Collection;
1717
use MongoDB\Driver\Cursor;
1818
use OutOfRangeException;
19+
use TypeError;
1920
use function array_map;
2021
use function array_merge;
2122
use function array_unshift;
2223
use function assert;
24+
use function func_get_arg;
25+
use function func_num_args;
26+
use function gettype;
2327
use function is_array;
28+
use function is_bool;
2429
use function sprintf;
2530

2631
/**
@@ -218,36 +223,63 @@ public function geoNear($x, $y = null) : Stage\GeoNear
218223
return $stage;
219224
}
220225

226+
// phpcs:disable Squiz.Commenting.FunctionComment.ExtraParamComment
221227
/**
222228
* Returns the assembled aggregation pipeline
223229
*
230+
* @param bool $applyFilters Whether to apply filters on the aggregation
231+
* pipeline stage
232+
*
224233
* For pipelines where the first stage is a $geoNear stage, it will apply
225234
* the document filters and discriminator queries to the query portion of
226235
* the geoNear operation. For all other pipelines, it prepends a $match stage
227236
* containing the required query.
237+
*
238+
* For aggregation pipelines that will be nested (e.g. in a facet stage),
239+
* you should not apply filters as this may cause wrong results to be
240+
* given.
228241
*/
229-
public function getPipeline() : array
242+
// phpcs:enable Squiz.Commenting.FunctionComment.ExtraParamComment
243+
public function getPipeline(/* bool $applyFilters = true */) : array
230244
{
245+
$applyFilters = func_num_args() > 0 ? func_get_arg(0) : true;
246+
247+
if (! is_bool($applyFilters)) {
248+
throw new TypeError(sprintf(
249+
'Argument 1 passed to %s must be of the type bool, %s given',
250+
__METHOD__,
251+
gettype($applyFilters)
252+
));
253+
}
254+
231255
$pipeline = array_map(
232256
static function (Stage $stage) {
233257
return $stage->getExpression();
234258
},
235259
$this->stages
236260
);
237261

238-
if ($this->getStage(0) instanceof Stage\GeoNear) {
239-
$pipeline[0]['$geoNear']['query'] = $this->applyFilters($pipeline[0]['$geoNear']['query']);
240-
} elseif ($this->getStage(0) instanceof Stage\IndexStats) {
262+
if ($this->getStage(0) instanceof Stage\IndexStats) {
241263
// Don't apply any filters when using an IndexStats stage: since it
242264
// needs to be the first pipeline stage, prepending a match stage
243265
// with discriminator information will not work
244266

267+
$applyFilters = false;
268+
}
269+
270+
if (! $applyFilters) {
245271
return $pipeline;
246-
} else {
247-
$matchExpression = $this->applyFilters([]);
248-
if ($matchExpression !== []) {
249-
array_unshift($pipeline, ['$match' => $matchExpression]);
250-
}
272+
}
273+
274+
if ($this->getStage(0) instanceof Stage\GeoNear) {
275+
$pipeline[0]['$geoNear']['query'] = $this->applyFilters($pipeline[0]['$geoNear']['query']);
276+
277+
return $pipeline;
278+
}
279+
280+
$matchExpression = $this->applyFilters([]);
281+
if ($matchExpression !== []) {
282+
array_unshift($pipeline, ['$match' => $matchExpression]);
251283
}
252284

253285
return $pipeline;

lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Facet.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function getExpression() : array
2828
{
2929
return [
3030
'$facet' => array_map(static function (Builder $builder) {
31-
return $builder->getPipeline();
31+
return $builder->getPipeline(false);
3232
}, $this->pipelines),
3333
];
3434
}

tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Documents\Group;
1212
use Documents\User;
1313
use MongoDB\Driver\ReadPreference;
14+
use MongoDB\Driver\WriteConcern;
1415

1516
class ReadPreferenceTest extends BaseTest
1617
{
@@ -21,7 +22,7 @@ public function setUp() : void
2122
$user = new User();
2223
$user->addGroup(new Group('Test'));
2324
$this->dm->persist($user);
24-
$this->dm->flush();
25+
$this->dm->flush(['writeConcern' => new WriteConcern('majority')]);
2526
$this->dm->clear();
2627
}
2728

@@ -34,6 +35,7 @@ public function testHintIsNotSetByDefault()
3435
$this->assertArrayNotHasKey('readPreference', $query->getQuery());
3536

3637
$user = $query->getSingleResult();
38+
$this->assertInstanceOf(User::class, $user);
3739

3840
$this->assertInstanceOf(PersistentCollectionInterface::class, $user->getGroups());
3941
$this->assertArrayNotHasKey(Query::HINT_READ_PREFERENCE, $user->getGroups()->getHints());
@@ -52,6 +54,7 @@ public function testHintIsSetOnQuery($readPreference, array $tags = [])
5254
$this->assertReadPreferenceHint($readPreference, $query->getQuery()['readPreference'], $tags);
5355

5456
$user = $query->getSingleResult();
57+
$this->assertInstanceOf(User::class, $user);
5558

5659
$this->assertInstanceOf(PersistentCollectionInterface::class, $user->getGroups());
5760
$this->assertReadPreferenceHint($readPreference, $user->getGroups()->getHints()[Query::HINT_READ_PREFERENCE], $tags);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\ODM\ODM\Tests\Functional\Ticket;
6+
7+
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
8+
use Doctrine\ODM\MongoDB\Tests\BaseTest;
9+
use function count;
10+
11+
class GH2157Test extends BaseTest
12+
{
13+
public function testFacetDiscriminatorMapCreation()
14+
{
15+
$this->dm->persist(new GH2157FirstType());
16+
$this->dm->persist(new GH2157FirstType());
17+
$this->dm->persist(new GH2157FirstType());
18+
$this->dm->persist(new GH2157FirstType());
19+
$this->dm->flush();
20+
21+
$result = $this->dm->createAggregationBuilder(GH2157FirstType::class)
22+
->project()
23+
->includeFields(['id'])
24+
->facet()
25+
->field('count')
26+
->pipeline(
27+
$this->dm->createAggregationBuilder(GH2157FirstType::class)
28+
->count('count')
29+
)
30+
->field('limitedResults')
31+
->pipeline(
32+
$this->dm->createAggregationBuilder(GH2157FirstType::class)
33+
->limit(2)
34+
)
35+
->execute()->toArray();
36+
37+
$this->assertEquals(4, $result[0]['count'][0]['count']);
38+
$this->assertEquals(2, count($result[0]['limitedResults']));
39+
}
40+
}
41+
42+
/**
43+
* @ODM\Document(collection="documents")
44+
* @ODM\InheritanceType("SINGLE_COLLECTION")
45+
* @ODM\DiscriminatorField("type")
46+
* @ODM\DiscriminatorMap({"firsttype"=GH2157FirstType::class, "secondtype"=GH2157SecondType::class})
47+
*/
48+
abstract class GH2157Abstract
49+
{
50+
/**
51+
* @ODM\Id
52+
*
53+
* @var string
54+
*/
55+
protected $id;
56+
}
57+
58+
/**
59+
* @ODM\Document
60+
*/
61+
class GH2157FirstType extends GH2157Abstract
62+
{
63+
}
64+
65+
/**
66+
* @ODM\Document
67+
*/
68+
class GH2157SecondType extends GH2157Abstract
69+
{
70+
}

0 commit comments

Comments
 (0)