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

Compute entity-level commit order for entity insertions #10689

Conversation

mpdude
Copy link
Contributor

@mpdude mpdude commented May 8, 2023

Make the UoW find a commit order for entity insertions not on the class, but at the entity level. This is the third step to break #10547 into smaller PRs suitable for reviewing. It uses the new topological sort implementation from #10592 and the refactoring from #10651.

Current situation

UnitOfWork::getCommitOrder() would compute the entity sequence on the class level with the following code:

foreach ($class->associationMappings as $assoc) {
if (! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) {
continue;
}
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
if (! $calc->hasNode($targetClass->name)) {
$calc->addNode($targetClass->name, $targetClass);
$newNodes[] = $targetClass;
}
$joinColumns = reset($assoc['joinColumns']);
$calc->addDependency($targetClass->name, $class->name, (int) empty($joinColumns['nullable']));

Suggested change

  • Instead of considering the classes of all entities that need to be inserted, updated or deleted, consider the new (inserted) entities only. We only need to find a sequence in situations where there are foreign key relationships between two new entities.
  • In the dependency graph, add edges for all to-one association target entities.
  • Make edges "optional" when the association is nullable.

Extra bonus

This is what the DALL·E AI thinks it looks like when the UnitOfWork is scheduling the sequence of entity insertions.

DALL·E 2023-05-08 18 32 54

@mpdude mpdude force-pushed the insert-entity-level-topo-sort branch 3 times, most recently from e007a7a to 2723132 Compare May 8, 2023 16:38
Comment on lines +1297 to +1299
// According to https://www.doctrine-project.org/projects/doctrine-orm/en/2.14/reference/annotations-reference.html#annref_joincolumn,
// the default for "nullable" is true. Unfortunately, it seems this default is not applied at the metadata driver, factory or other
// level, but in fact we may have an undefined 'nullable' key here, so we must assume that default here as well.
Copy link
Member

Choose a reason for hiding this comment

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

IIRC, join columns are nullable by default for one-to-many relations and not nullable for many-to-many relations. If at this point you need to know these defaults, maybe ClassMetadata should amend the nullable key if it's missing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have similar code here...

foreach ($joinColumns as $joinColumn) {
if (! isset($joinColumn['nullable']) || $joinColumn['nullable']) {
return 'LEFT JOIN';
}
}

... or here ...

foreach ($joinColumns as $joinColumn) {
if (isset($joinColumn['nullable']) && ! $joinColumn['nullable']) {
return false;
}
}

... and for fields in general (not only join columns) for example here:

return $mapping !== false && isset($mapping['nullable']) && $mapping['nullable'];

I agree that it would be better if the nullable (and possibly other keys in those join column definition or field mapping configuration arrays) were initialized with defaults in the CMF; but that's out of scope here, so can we leave it for another PR?

lib/Doctrine/ORM/UnitOfWork.php Outdated Show resolved Hide resolved
@mpdude mpdude force-pushed the insert-entity-level-topo-sort branch 2 times, most recently from 3394356 to 38f3faf Compare May 9, 2023 11:08
This is the third step to break doctrine#10547 into smaller PRs suitable for reviewing. It uses the new topological sort implementation from doctrine#10592 and the refactoring from doctrine#10651 to compute the UoW's commit order for entity insertions not on the entity class level, but for single entities and their actual dependencies instead.

 #### Current situation

`UnitOfWork::getCommitOrder()` would compute the entity sequence on the class level with the following code:

https://github.com/doctrine/orm/blob/70477d81e96c0044ad6fd8c13c37b2270d082792/lib/Doctrine/ORM/UnitOfWork.php#L1310-L1325

 #### Suggested change

* Instead of considering the classes of all entities that need to be inserted, updated or deleted, consider the new (inserted) entities only. We only need to find a sequence in situations where there are foreign key relationships between two _new_ entities.
* In the dependency graph, add edges for all to-one association target entities.
* Make edges "optional" when the association is nullable.

 #### Test changes

I have not tried to fully understand the few changes necessary to fix the tests. My guess is that those are edge cases where the insert order changed and we need to consider this during clean-up.

Keep in mind that many of the functional tests we have assume that entities have IDs assigned in the order that they were added to the EntityManager. That does not change – so the order of entities is generally stable, equal to the previous implementation.
@mpdude mpdude force-pushed the insert-entity-level-topo-sort branch from 38f3faf to d76fc4e Compare May 31, 2023 07:17
@mpdude mpdude force-pushed the insert-entity-level-topo-sort branch from 2e02bbd to aa27b3a Compare May 31, 2023 07:32
@mpdude
Copy link
Contributor Author

mpdude commented May 31, 2023

Rebased and resolved conflicts after #10566 has been merged.

I wanted to spare clean-ups (remove the now unused UoW::getCommitOrder() method) for another PR, but static analysis checks forced me to remove it right away.

*
* @return list<ClassMetadata>
*/
private function getCommitOrder(): array
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As of this PR, this method is no longer used.

@mpdude
Copy link
Contributor Author

mpdude commented May 31, 2023

@greg0ire and @SenseException

I think this is the last PR regarding changes in implementation. Once we've got this merged, I can start making PRs to pick/collect/provide (regression) tests for all the fixed issues.

That means the hard work would be done, hopefully reviewing tests added is easy :-)

@mpdude
Copy link
Contributor Author

mpdude commented Jun 2, 2023

@greg0ire could you 👀 please? 🙏🏻

@mpdude
Copy link
Contributor Author

mpdude commented Jun 2, 2023

Please merge this, so I can continue with more tests on the entity-level-commit-order branch.

@greg0ire greg0ire merged commit 330c0bc into doctrine:entity-level-commit-order Jun 2, 2023
41 checks passed
@mpdude mpdude deleted the insert-entity-level-topo-sort branch June 2, 2023 17:39
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