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

Extra update for self-referencing Many-To-One with NONE generator strategy during persist #7877

Closed
sylfabre opened this issue Oct 23, 2019 · 5 comments · Fixed by #10735
Closed
Labels

Comments

@sylfabre
Copy link
Contributor

Bug Report

Q A
BC Break no
Version 2.6.4

Summary

Using a self-referencing entity with a Many-To-One relationship set during the __construct() call with a NONE strategy for id generation leads to two queries:

  • first an INSERT
  • then an UPDATE for the relationship

Current behavior

  • this requires the relationship related column to accept NULL for the first INSERT

[2019-10-23 16:04:21] doctrine.DEBUG: INSERT INTO organization (id, legal_parent_id) VALUES (?, ?, ?, ?, ?, ?, ?) {"1":"[object] (Ramsey\\Uuid\\Uuid: \"c68555b0-f5ae-11e9-914e-38baf82a0624\")","2":null} []

  • this leads to a useless UPDATE query

[2019-10-23 16:04:21] doctrine.DEBUG: UPDATE organization SET legal_parent_id = ? WHERE id = ? ["[object] (Ramsey\\Uuid\\Uuid: \"c68555b0-f5ae-11e9-914e-38baf82a0624\")","[object] (Ramsey\\Uuid\\Uuid: \"c68555b0-f5ae-11e9-914e-38baf82a0624\")"] []

How to reproduce

CREATE TABLE `organization` (
  `id` binary(16) NOT NULL COMMENT '(DC2Type:uuid_binary_ordered_time)',
  `legal_parent_id` BINARY(16) NOT NULL COMMENT '(DC2Type:uuid_binary_ordered_time)',
  PRIMARY KEY (`id`),
  CONSTRAINT FK_C1EE637C9EB3F521 FOREIGN KEY (legal_parent_id) REFERENCES organization (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
<?php

declare(strict_types=1);

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * An Organization
 * @ORM\Entity(repositoryClass="App\Repository\OrganizationRepository")
 */
class Organization
{
    public function __construct()
    {
        $factory = clone \Ramsey\Uuid\Uuid::getFactory();
        $codec = new \Ramsey\Uuid\Codec\OrderedTimeCodec(
            $factory->getUuidBuilder()
        );
        $factory->setCodec($codec);
		$this->id = $factory->uuid1();
        $this->legalParent = $this;
        parent::__construct();
    }

    /**
     * Unique ID of the entity
     *
     * @ORM\Id()
     * @ORM\GeneratedValue(strategy="NONE")
     * @ORM\Column(type="uuid_binary_ordered_time", unique=true)
     */
    private $id;

    public function getId(): ?string
    {
        return $this->id->__toString();
    }

    /**
     * @var self
     * @ORM\ManyToOne(targetEntity="App\Entity\Organization")
     * @ORM\JoinColumn(fieldName="legal_parent_id", referencedColumnName="id", nullable=false)
     */
    protected $legalParent;

    public function getLegalParent(): self
    {
        return $this->legalParent;
    }

    public function setLegalParent(self $legalParent): self
    {
        $this->legalParent = $legalParent;

        return $this;
    }
}

Expected behavior

  • Only the INSERT query with legal_parent_id not NULL

[2019-10-23 16:04:21] doctrine.DEBUG: INSERT INTO organization (id, legal_parent_id) VALUES (?, ?, ?, ?, ?, ?, ?) {"1":"[object] (Ramsey\\Uuid\\Uuid: \"c68555b0-f5ae-11e9-914e-38baf82a0624\")","2":[object] (Ramsey\\Uuid\\Uuid: \"c68555b0-f5ae-11e9-914e-38baf82a0624\")} []

Possible solution

This comes from vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php

image

We could check if spl_object_hash($newVal) === spl_object_hash($entity) and if ClassMetadataInfo::GENERATOR_TYPE_NONE === $this->class->generatorType. If yes, then do not set $newVal = null; and do not schedule extra update.

I can provide a PR if you want

@SenseException
Copy link
Member

Thank you for opening this issue. Pull requests are welcome if you like to provide one. The PR will also need integration test(s) with an entity close to the one of your example, but with a classic integer id. I assume your example was close to the actual use case of the project you're working on.

sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Oct 30, 2019
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Oct 30, 2019
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Oct 30, 2019
@sylfabre
Copy link
Contributor Author

@SenseException PR done #7882

sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Oct 30, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Oct 30, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 1, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 1, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 1, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 1, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 1, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 1, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 1, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 1, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 1, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 1, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 1, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 1, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 1, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 1, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 2, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 2, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 2, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 2, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue Nov 3, 2019
BaseEntityPersister can save an extra UPDATE query when it
persists a self-referencing entity using an application
generated identifier

Fixes doctrine#7877
@sylfabre sylfabre mentioned this issue May 30, 2023
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue May 30, 2023
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue May 30, 2023
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue May 30, 2023
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue May 30, 2023
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue May 31, 2023
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue May 31, 2023
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue May 31, 2023
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue May 31, 2023
sylfabre added a commit to sylfabre/doctrine-orm that referenced this issue May 31, 2023
@mpdude
Copy link
Contributor

mpdude commented Jun 1, 2023

Out of curiosity, do you really care about one UPDATE query more or less?

Or is the real motivation that a self-referencing (at the class level) association like this has to be defined as NULLable just because of the edge case that an entity might reference itself (at the entity level)?

I find the latter argument much stronger.

If that’s the case, we should also write the tests in a way to show that the entities can be inserted even with a non-NULLable association, not focus so much on the query count.

@sylfabre
Copy link
Contributor Author

sylfabre commented Jun 1, 2023

@mpdude The real motivation is having to define a NULLable column.

You're right about the test, I'll work on a better version in my PR

mpdude added a commit to mpdude/doctrine2 that referenced this issue Jun 1, 2023
…hen using application-provided IDs

The change makes the `BasicEntityPersister` not schedule an extra update in the case of an entity referencing itself and having an application-provided ID (the "NONE" generator strategy).

While it looks like a special corner case, the INSERTion of a NULL value plus the extra update require the self-referencing column to be defined as NULLable. This is only an issue for the self-referencing entity case. All other associations work naturally.

Fixes doctrine#7877, closes doctrine#7882.

Co-authored-by: Sylvain Fabre <[email protected]>
mpdude added a commit to mpdude/doctrine2 that referenced this issue Jun 1, 2023
…es for association-target entities that use application-provided IDs (the "NONE" generator strategy). For this generator strategy, we may assume that IDs are already present at the time the insert is executed.

The benefit is that now the join column can be defined as not-NULLable, since the ORM no longer uses temporary NULL values.

However, care must be taken:

Extra updates are not only about the question of having IDs for the associated entities available, but also about having those entities in the database already (to satisfy foreign key constaints).

The latter will become easier with doctrine#10547, since the UoW will take care of that detail when computing the commit order.

But without that detail, we might break use cases that currently work – that's cases where the extra update gives us the extra time to insert the referred-to entity first.

Fixes doctrine#7877, closes doctrine#7882.

Co-authored-by: Sylvain Fabre <[email protected]>
mpdude added a commit to mpdude/doctrine2 that referenced this issue Jun 2, 2023
…hen using application-provided IDs

This change improves scheduling of extra updates in the `BasicEntityPersister`.

Extra updates can be avoided when
* the referred-to entity has already been inserted during the current insert batch/transaction
* we have a self-referencing entity with application-provided ID values (the `NONE` generator strategy).

As a corollary, with this change applications that provide their own IDs can define self-referencing associations as not NULLable.

I am considering this a bugfix since the ORM previously executed additional queries that were not strictly necessary, and that required users to work with NULLable columns where conceptually a non-NULLable column would be valid and more expressive.

One caveat, though:

In the absence of entity-level commit ordering (doctrine#10547), it is not guaranteed that entities with self-references (at the class level) will be inserted in a suitable order. The order depends on the sequence in which the entities were added with `persist()`.

Fixes doctrine#7877, closes doctrine#7882.

Co-authored-by: Sylvain Fabre <[email protected]>
mpdude added a commit to mpdude/doctrine2 that referenced this issue Jun 2, 2023
…hen using application-provided IDs

This change improves scheduling of extra updates in the `BasicEntityPersister`.

Extra updates can be avoided when
* the referred-to entity has already been inserted during the current insert batch/transaction
* we have a self-referencing entity with application-provided ID values (the `NONE` generator strategy).

As a corollary, with this change applications that provide their own IDs can define self-referencing associations as not NULLable.

I am considering this a bugfix since the ORM previously executed additional queries that were not strictly necessary, and that required users to work with NULLable columns where conceptually a non-NULLable column would be valid and more expressive.

One caveat, though:

In the absence of entity-level commit ordering (doctrine#10547), it is not guaranteed that entities with self-references (at the class level) will be inserted in a suitable order. The order depends on the sequence in which the entities were added with `persist()`.

Fixes doctrine#7877, closes doctrine#7882.

Co-authored-by: Sylvain Fabre <[email protected]>
mpdude added a commit to mpdude/doctrine2 that referenced this issue Jun 2, 2023
…hen using application-provided IDs

This change improves scheduling of extra updates in the `BasicEntityPersister`.

Extra updates can be avoided when
* the referred-to entity has already been inserted during the current insert batch/transaction
* we have a self-referencing entity with application-provided ID values (the `NONE` generator strategy).

As a corollary, with this change applications that provide their own IDs can define self-referencing associations as not NULLable.

I am considering this a bugfix since the ORM previously executed additional queries that were not strictly necessary, and that required users to work with NULLable columns where conceptually a non-NULLable column would be valid and more expressive.

One caveat, though:

In the absence of entity-level commit ordering (doctrine#10547), it is not guaranteed that entities with self-references (at the class level) will be inserted in a suitable order. The order depends on the sequence in which the entities were added with `persist()`.

Fixes doctrine#7877, closes doctrine#7882.

Co-authored-by: Sylvain Fabre <[email protected]>
mpdude added a commit to mpdude/doctrine2 that referenced this issue Jun 2, 2023
…hen using application-provided IDs

This change improves scheduling of extra updates in the `BasicEntityPersister`.

Extra updates can be avoided when
* the referred-to entity has already been inserted during the current insert batch/transaction
* we have a self-referencing entity with application-provided ID values (the `NONE` generator strategy).

As a corollary, with this change applications that provide their own IDs can define self-referencing associations as not NULLable.

I am considering this a bugfix since the ORM previously executed additional queries that were not strictly necessary, and that required users to work with NULLable columns where conceptually a non-NULLable column would be valid and more expressive.

One caveat, though:

In the absence of entity-level commit ordering (doctrine#10547), it is not guaranteed that entities with self-references (at the class level) will be inserted in a suitable order. The order depends on the sequence in which the entities were added with `persist()`.

Fixes doctrine#7877, closes doctrine#7882.

Co-authored-by: Sylvain Fabre <[email protected]>
mpdude added a commit to mpdude/doctrine2 that referenced this issue Jul 7, 2023
…hen using application-provided IDs

This change improves scheduling of extra updates in the `BasicEntityPersister`.

Extra updates can be avoided when
* the referred-to entity has already been inserted during the current insert batch/transaction
* we have a self-referencing entity with application-provided ID values (the `NONE` generator strategy).

As a corollary, with this change applications that provide their own IDs can define self-referencing associations as not NULLable.

I am considering this a bugfix since the ORM previously executed additional queries that were not strictly necessary, and that required users to work with NULLable columns where conceptually a non-NULLable column would be valid and more expressive.

One caveat, though:

In the absence of entity-level commit ordering (doctrine#10547), it is not guaranteed that entities with self-references (at the class level) will be inserted in a suitable order. The order depends on the sequence in which the entities were added with `persist()`.

Fixes doctrine#7877, closes doctrine#7882.

Co-authored-by: Sylvain Fabre <[email protected]>
derrabus pushed a commit that referenced this issue Jul 8, 2023
…hen using application-provided IDs (#10735)

This change improves scheduling of extra updates in the `BasicEntityPersister`.

Extra updates can be avoided when
* the referred-to entity has already been inserted during the current insert batch/transaction
* we have a self-referencing entity with application-provided ID values (the `NONE` generator strategy).

As a corollary, with this change applications that provide their own IDs can define self-referencing associations as not NULLable.

I am considering this a bugfix since the ORM previously executed additional queries that were not strictly necessary, and that required users to work with NULLable columns where conceptually a non-NULLable column would be valid and more expressive.

One caveat, though:

In the absence of entity-level commit ordering (#10547), it is not guaranteed that entities with self-references (at the class level) will be inserted in a suitable order. The order depends on the sequence in which the entities were added with `persist()`.

Fixes #7877, closes #7882.

Co-authored-by: Sylvain Fabre <[email protected]>
@sylfabre
Copy link
Contributor Author

Fixed by #10735

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
3 participants