-
-
Notifications
You must be signed in to change notification settings - Fork 972
Hotfix/filter eager loading fetch eager #1028
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,8 @@ | |
| use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; | ||
| use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; | ||
| use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; | ||
| use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeItem; | ||
| use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeLabel; | ||
| use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation; | ||
| use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar; | ||
| use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Foo; | ||
|
|
@@ -37,8 +39,12 @@ public function testIsNoForceEagerCollectionAttributes() | |
| ], | ||
| ], null)); | ||
|
|
||
| $em = $this->prophesize(EntityManager::class); | ||
| $em->getClassMetadata(DummyCar::class)->shouldBeCalled()->willReturn(new ClassMetadataInfo(DummyCar::class)); | ||
|
|
||
| $qb = $this->prophesize(QueryBuilder::class); | ||
| $qb->getDQLPart('where')->shouldNotBeCalled(); | ||
| $qb->getEntityManager()->willReturn($em); | ||
|
|
||
| $queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class); | ||
|
|
||
|
|
@@ -48,13 +54,17 @@ public function testIsNoForceEagerCollectionAttributes() | |
|
|
||
| public function testIsNoForceEagerResource() | ||
| { | ||
| $em = $this->prophesize(EntityManager::class); | ||
| $em->getClassMetadata(DummyCar::class)->shouldBeCalled()->willReturn(new ClassMetadataInfo(DummyCar::class)); | ||
|
|
||
| $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); | ||
| $resourceMetadataFactoryProphecy->create(DummyCar::class)->willReturn(new ResourceMetadata(DummyCar::class, null, null, null, [ | ||
| 'get' => [], | ||
| ], ['force_eager' => false])); | ||
|
|
||
| $qb = $this->prophesize(QueryBuilder::class); | ||
| $qb->getDQLPart('where')->shouldNotBeCalled(); | ||
| $qb->getEntityManager()->willReturn($em); | ||
|
|
||
| $queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class); | ||
|
|
||
|
|
@@ -64,13 +74,17 @@ public function testIsNoForceEagerResource() | |
|
|
||
| public function testIsForceEagerConfig() | ||
| { | ||
| $em = $this->prophesize(EntityManager::class); | ||
| $em->getClassMetadata(DummyCar::class)->shouldBeCalled()->willReturn(new ClassMetadataInfo(DummyCar::class)); | ||
|
|
||
| $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); | ||
| $resourceMetadataFactoryProphecy->create(DummyCar::class)->willReturn(new ResourceMetadata(DummyCar::class, null, null, null, [ | ||
| 'get' => [], | ||
| ])); | ||
|
|
||
| $qb = $this->prophesize(QueryBuilder::class); | ||
| $qb->getDQLPart('where')->shouldNotBeCalled(); | ||
| $qb->getEntityManager()->willReturn($em); | ||
|
|
||
| $queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class); | ||
|
|
||
|
|
@@ -80,11 +94,15 @@ public function testIsForceEagerConfig() | |
|
|
||
| public function testHasNoWherePart() | ||
| { | ||
| $em = $this->prophesize(EntityManager::class); | ||
| $em->getClassMetadata(DummyCar::class)->shouldBeCalled()->willReturn(new ClassMetadataInfo(DummyCar::class)); | ||
|
|
||
| $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); | ||
| $resourceMetadataFactoryProphecy->create(DummyCar::class)->willReturn(new ResourceMetadata(DummyCar::class)); | ||
|
|
||
| $qb = $this->prophesize(QueryBuilder::class); | ||
| $qb->getDQLPart('where')->shouldBeCalled()->willReturn(null); | ||
| $qb->getEntityManager()->willReturn($em); | ||
|
|
||
| $queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class); | ||
|
|
||
|
|
@@ -94,12 +112,16 @@ public function testHasNoWherePart() | |
|
|
||
| public function testHasNoJoinPart() | ||
| { | ||
| $em = $this->prophesize(EntityManager::class); | ||
| $em->getClassMetadata(DummyCar::class)->shouldBeCalled()->willReturn(new ClassMetadataInfo(DummyCar::class)); | ||
|
|
||
| $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); | ||
| $resourceMetadataFactoryProphecy->create(DummyCar::class)->willReturn(new ResourceMetadata(DummyCar::class)); | ||
|
|
||
| $qb = $this->prophesize(QueryBuilder::class); | ||
| $qb->getDQLPart('where')->shouldBeCalled()->willReturn(new Expr\Andx()); | ||
| $qb->getDQLPart('join')->shouldBeCalled()->willReturn(null); | ||
| $qb->getEntityManager()->willReturn($em); | ||
|
|
||
| $queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class); | ||
|
|
||
|
|
@@ -162,12 +184,12 @@ public function testHiddenOrderBy() | |
| $filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, 'get'); | ||
|
|
||
| $expected = <<<SQL | ||
| SELECT o, CASE WHEN o.dateCreated IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dateCreated_null_rank | ||
| FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar o | ||
| LEFT JOIN o.colors colors | ||
| SELECT o, CASE WHEN o.dateCreated IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dateCreated_null_rank | ||
| FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar o | ||
| LEFT JOIN o.colors colors | ||
| WHERE o IN( | ||
| SELECT o_2 FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar o_2 | ||
| LEFT JOIN o_2.colors colors_2 | ||
| SELECT o_2 FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar o_2 | ||
| LEFT JOIN o_2.colors colors_2 | ||
| WHERE o_2.colors = :foo | ||
| ) ORDER BY _o_dateCreated_null_rank DESC ASC | ||
| SQL; | ||
|
|
@@ -202,15 +224,15 @@ public function testGroupBy() | |
| $filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, 'get'); | ||
|
|
||
| $expected = <<<SQL | ||
| SELECT o, count(o.id) as counter | ||
| FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar o | ||
| LEFT JOIN o.colors colors WHERE o | ||
| SELECT o, count(o.id) as counter | ||
| FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar o | ||
| LEFT JOIN o.colors colors WHERE o | ||
| IN( | ||
| SELECT o_2 FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar o_2 | ||
| LEFT JOIN o_2.colors colors_2 | ||
| SELECT o_2 FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar o_2 | ||
| LEFT JOIN o_2.colors colors_2 | ||
| WHERE o_2.colors = :foo | ||
| ) | ||
| GROUP BY o.colors HAVING counter > 3 | ||
| ) | ||
| GROUP BY o.colors HAVING counter > 3 | ||
| ORDER BY o.colors ASC | ||
| SQL; | ||
|
|
||
|
|
@@ -248,19 +270,74 @@ public function testCompositeIdentifiers() | |
| $filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), CompositeRelation::class, 'get'); | ||
|
|
||
| $expected = <<<SQL | ||
| SELECT o | ||
| FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation o | ||
| INNER JOIN o.compositeItem item | ||
| INNER JOIN o.compositeLabel label | ||
| SELECT o | ||
| FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation o | ||
| INNER JOIN o.compositeItem item | ||
| INNER JOIN o.compositeLabel label | ||
| WHERE o.item IN( | ||
| SELECT IDENTITY(o_2.item) FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation o_2 | ||
| INNER JOIN o_2.compositeItem item_2 | ||
| INNER JOIN o_2.compositeLabel label_2 | ||
| WHERE item_2.field1 = :foo | ||
| ) AND o.label IN( | ||
| SELECT IDENTITY(o_2.label) FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation o_2 | ||
| INNER JOIN o_2.compositeItem item_2 | ||
| INNER JOIN o_2.compositeLabel label_2 | ||
| WHERE item_2.field1 = :foo | ||
| ) | ||
| SQL; | ||
|
|
||
| $this->assertEquals($this->toDQLString($expected), $qb->getDQL()); | ||
| } | ||
|
|
||
| public function testFetchEagerWithNoForceEager() | ||
| { | ||
| $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); | ||
| $resourceMetadataFactoryProphecy->create(CompositeRelation::class)->willReturn(new ResourceMetadata(CompositeRelation::class)); | ||
|
|
||
| $classMetadata = new ClassMetadataInfo(CompositeRelation::class); | ||
| $classMetadata->isIdentifierComposite = true; | ||
| $classMetadata->identifier = ['item', 'label']; | ||
| $classMetadata->associationMappings = [ | ||
| 'item' => ['fetch' => 3, 'joinColumns' => [['nullable' => false]], 'targetEntity' => CompositeItem::class], | ||
| 'label' => ['fetch' => 3, 'joinColumns' => [['nullable' => false]], 'targetEntity' => CompositeLabel::class], | ||
| ]; | ||
|
|
||
| $em = $this->prophesize(EntityManager::class); | ||
| $em->getExpressionBuilder()->shouldBeCalled()->willReturn(new Expr()); | ||
| $em->getClassMetadata(CompositeRelation::class)->shouldBeCalled()->willReturn($classMetadata); | ||
|
|
||
| $qb = new QueryBuilder($em->reveal()); | ||
|
|
||
| $qb->select('o') | ||
| ->from(CompositeRelation::class, 'o') | ||
| ->innerJoin('o.compositeItem', 'item') | ||
| ->innerJoin('o.compositeLabel', 'label') | ||
| ->where('item.field1 = :foo') | ||
| ->setParameter('foo', 1); | ||
|
|
||
| $queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class); | ||
| $queryNameGenerator->generateJoinAlias('item')->shouldBeCalled()->willReturn('item_2'); | ||
| $queryNameGenerator->generateJoinAlias('label')->shouldBeCalled()->willReturn('label_2'); | ||
| $queryNameGenerator->generateJoinAlias('o')->shouldBeCalled()->willReturn('o_2'); | ||
|
|
||
| $filterEagerLoadingExtension = new FilterEagerLoadingExtension($resourceMetadataFactoryProphecy->reveal(), false); | ||
| $filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), CompositeRelation::class, 'get'); | ||
|
|
||
| $expected = <<<SQL | ||
| SELECT o | ||
| FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation o | ||
| INNER JOIN o.compositeItem item | ||
| INNER JOIN o.compositeLabel label | ||
| WHERE o.item IN( | ||
| SELECT IDENTITY(o_2.item) FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation o_2 | ||
| INNER JOIN o_2.compositeItem item_2 | ||
| INNER JOIN o_2.compositeLabel label_2 | ||
| SELECT IDENTITY(o_2.item) FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation o_2 | ||
| INNER JOIN o_2.compositeItem item_2 | ||
| INNER JOIN o_2.compositeLabel label_2 | ||
| WHERE item_2.field1 = :foo | ||
| ) AND o.label IN( | ||
| SELECT IDENTITY(o_2.label) FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation o_2 | ||
| INNER JOIN o_2.compositeItem item_2 | ||
| INNER JOIN o_2.compositeLabel label_2 | ||
| SELECT IDENTITY(o_2.label) FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation o_2 | ||
| INNER JOIN o_2.compositeItem item_2 | ||
| INNER JOIN o_2.compositeLabel label_2 | ||
| WHERE item_2.field1 = :foo | ||
| ) | ||
| SQL; | ||
|
|
@@ -270,6 +347,6 @@ public function testCompositeIdentifiers() | |
|
|
||
| private function toDQLString(string $dql): string | ||
| { | ||
| return preg_replace('/\\r\\n|\\n/', '', str_replace(' ', '', $dql)); | ||
| return preg_replace(['/\s+/', '/\(\s/', '/\s\)/'], [' ', '(', ')'], $dql); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be better \o/
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually this is unsafe... It might change whitespace within a SQL literal (string). Lol...
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Of course, as said before it makes tests more readable :). |
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to extract this logic out for reuse between
EagerLoadingExtensionandFilterEagerLoadingExtension?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want to to this to avoid a 2-pass recursive call in
EagerLoadingExtension. If we want to extract this logic we need to add cache. I obviously agree that we should add a new class with theshouldEagerLoadlogic.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't this be extracted in another PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it can and should, +1 to do this in another PR as it's not a priority.