Skip to content
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

Updates regarding doctrine performances with associations #717

Merged
merged 2 commits into from
Sep 6, 2016

Conversation

soyuka
Copy link
Member

@soyuka soyuka commented Aug 31, 2016

Q A
Bug fix? yes
New feature? no
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets
License MIT
Doc PR
  • Do not join twice when filtering on an association (EagerLoadingExtension + SearchFilter) (tested)
  • Inner join instead of left join relations when those aren't nullable EagerLoadingExtension (tested)
  • Do not to fetch properties that are not serializable! (tested)
  • Discuss Query::HINT_FORCE_PARTIAL_LOAD to avoid fetching OneToOne relations when we don't need them

@@ -354,4 +367,21 @@ private function normalizeValues(array $values) : array

return array_values($values);
}

private function hasJoin($queryBuilder, $alias, $association)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing type hints.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way I think getJoin returning null when the join doesn't exist would be a cleaner API.

@dunglas
Copy link
Member

dunglas commented Aug 31, 2016

Great improvements!


//@TODO useless at the moment, but Doctrine queries non-necessary relations because addSelect($associationAlias) lazy fetch every association
//By using this array of properties, it Lazy fetch association, which are initially in an INNER JOIN (fetches them twice ?!)
//If I try to use associationAlias when it has every property, but the properties array when it doesn't it fails because it can not fetch parent association
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still an issue, @Simperfit mind taking a look? I think you've a good background with Doctrine maybe you can help figuring this out :/.

//
//By using this array of properties, Doctrine will lazy fetch every associations, which are initially in an INNER JOIN (fetches them twice ?!)
//If I try to use associationAlias when it has every property (so that Doctrine does not do sub queries on top of the initial one) it fails because it can not fetch parent association (like internally they try to find aliasX, but no aliasX is selected, we've instead aliasX.id, aliasX.name)
//I obviously tried to specify `WITH` `aliasX.id = aliasY.id` without success
foreach ($this->getMetadataProperties($mapping['targetEntity']) as $property => $propertyMetadata) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please read my story, if you have any clue or idea to help me think it'd be wonderful :D. ping @Simperfit

Copy link
Member Author

@soyuka soyuka Aug 31, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to myself:
use partial ref ref2 ref3

@@ -131,16 +131,16 @@ public function testApplyToItem()
$classMetadataProphecy->getAssociationNames()->shouldBeCalled()->willReturn([0 => 'relatedDummy', 'relatedDummy2']);
$classMetadataProphecy->associationMappings = [
'relatedDummy' => [
'fetch' => 3, 'joinColumns' => [['nullable' => true]], 'targetEntity' => DummyRelated::class
'fetch' => 3, 'joinColumns' => [['nullable' => true]], 'targetEntity' => DummyRelated::class,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 on each line please...

@soyuka
Copy link
Member Author

soyuka commented Sep 1, 2016

Ready for review :D !

This improves the complex queries a lot! Strong impact on performances, mainly on collections with lots of related fields (almost counts in seconds).

@soyuka soyuka changed the title [WIP] Updates regarding doctrine performances with associations Updates regarding doctrine performances with associations Sep 1, 2016
*
* @return string the new association alias
*/
protected function addJoinOnce($queryBuilder, $queryNameGenerator, $alias, $association): string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why protected?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing typehints btw

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Protected because it's called from filters (ie SearchFilter).

@soyuka soyuka force-pushed the improve-doctrine-query branch 2 times, most recently from 52e93dd to 20e380e Compare September 1, 2016 12:34
@dunglas
Copy link
Member

dunglas commented Sep 5, 2016

Is it ready to be merged?

@soyuka
Copy link
Member Author

soyuka commented Sep 5, 2016

Apart from my comment on query hint it's good to go.


$queryBuilder->addSelect(sprintf('partial %s.{%s}', $associationAlias, implode(',', $select)));

$relationAlias = $relationAlias.++$j;
Copy link
Contributor

@Simperfit Simperfit Sep 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any reason that $j could not be $nbOfJoin or smth like that. IMO it's not that clear

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's not really the number of join, it's a way to keep the alias unique across relation, ie a random number.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid random numbers in DQL as much as possible to trigger the Doctrine cache as often as possible.

Copy link
Member Author

@soyuka soyuka Sep 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the same in every query ofc. Just giving a explanation to the simple variable name.

@Simperfit
Copy link
Contributor

@soyuka Amazing work ;)

@soyuka soyuka force-pushed the improve-doctrine-query branch 3 times, most recently from 140ade9 to d31b39f Compare September 6, 2016 07:32
@soyuka
Copy link
Member Author

soyuka commented Sep 6, 2016

@dunglas I added a decoration possibility, not sure if I have to catch an undefined method call though. Should I add the same for the ItemDataProvider? Should I add an interface to be implemented by decorators using getQuery?

@soyuka soyuka force-pushed the improve-doctrine-query branch 3 times, most recently from 62832fd to ba129bc Compare September 6, 2016 07:50
@soyuka
Copy link
Member Author

soyuka commented Sep 6, 2016

Done thanks. I've added the same on ItemDataProvider for the sake of consistency.

@soyuka soyuka force-pushed the improve-doctrine-query branch 2 times, most recently from 98b82ff to 6568143 Compare September 6, 2016 07:52
@dunglas
Copy link
Member

dunglas commented Sep 6, 2016

@soyuka After double checking, isn't this use case already supported by the QueryResultExtensionInterface introduced by @sroze?

Someone wanting to set Query::HINT_FORCE_PARTIAL_LOAD can register an extension having a getResult method doing it. WDYT?

@soyuka
Copy link
Member Author

soyuka commented Sep 6, 2016

What I don't like with this interface is that it expects the query to return a result. For example, if you wanted to use the PaginationExtension but also modify some hints on Query it won't be possible. Thing is, Query can only be modified only once every QueryBuilder tasks are done...

@dunglas
Copy link
Member

dunglas commented Sep 6, 2016

But for such cases, decoration can be used (decorating the PaginationExtension, and especially its getResult method for instance).

@soyuka
Copy link
Member Author

soyuka commented Sep 6, 2016

Indeed, this is what I was going to say. For example:

/**
 * {@inheritdoc}
 */
public function getResult(QueryBuilder $queryBuilder)
{
    $query = $queryBuilder->getQuery();
    $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);

    $doctrineOrmPaginator = new DoctrineOrmPaginator($query, $this->useFetchJoinCollection($queryBuilder));
    $doctrineOrmPaginator->setUseOutputWalkers($this->useOutputWalkers($queryBuilder));

    return new Paginator($doctrineOrmPaginator);
}

@soyuka
Copy link
Member Author

soyuka commented Sep 6, 2016

Wait for tests to pass and to me it's good to go.

@dunglas
Copy link
Member

dunglas commented Sep 6, 2016

So, can you revert the last commit introducing the $decorated constructor parameter?

* @param string $alias
* @param string $association the association field
*
* @return Doctrine\ORM\Query\Expr\Join|null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a use statement instead?

- use innerJoin when possible (relation is not nullable)
- use partial selects and skip unreadable properties
- improve coverage
@dunglas dunglas merged commit 36b98c4 into api-platform:master Sep 6, 2016
@dunglas
Copy link
Member

dunglas commented Sep 6, 2016

Thanks @soyuka

@soyuka soyuka deleted the improve-doctrine-query branch September 9, 2016 16:01
magarzon pushed a commit to magarzon/core that referenced this pull request Feb 12, 2017
Updates regarding doctrine performances with associations
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants