IDENTITY identifier strategy for PostgreSQL breaks when inherited from MappedSuperclass #7216

Closed
opened 2026-01-22 15:47:08 +01:00 by admin · 18 comments
Owner

Originally created by @DemoniacDeath on GitHub (Sep 1, 2023).

BC Break Report

Q A
BC Break yes
Version 2.16.0

Summary

Persisting an @Entity which has it's @Id defined in it's parent (@MappedSuperclass) with IDENTITY generator strategy on PostgreSQL platform.
It seems that the breaking change occured in this PR https://github.com/doctrine/orm/pull/10455, specifically changes in ClassMetadataFactory that removed the condtion of $rootEntityFound for calling inheritIdGeneratorMapping()

Previous behavior

In version 2.15.5 the IdentityGenerator used to retrieve inserted id came from the Entity itself with $sequenceName matching the schema. So the value was retrieved from the correct sequence.

Current behavior

Now the IdentityGenerator used to retrieve inserted id comes from the parent @MappedSuperclass and $sequenceName is wrong (name of parent class + sequence suffix) and non-existent in schema, so a PostgreSQL error about relation not existing is thrown.

How to reproduce

E.g. (irrelevant code and domain-specific terminology is ommited):

/**
 * @ORM\MappedSuperclass
 */
abstract class AbstractTaskOperatorHistoryRecord
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private ?int $id = null;
}
/**
 * @ORM\Entity()
 * @ORM\Table(name="foo_bar_task_operator_history")
 */
class FooBarTaskOperatorHistoryRecord extends AbstractTaskOperatorHistoryRecord
{
}
$this->_em->persist(new FooBarTaskOperatorHistoryRecord());
$this->_em->flush();

The PostgreSQL sequence used by id column in foo_bar_task_operator_history table is foo_bar_task_operator_history_id_seq.
The sequence used to retrieve inserted id was foo_bar_task_operator_history_id_seq before the breaking change. Now it is trying to get the value from abstracttaskoperatorhistoryrecord_id_seq which obviously does not and should not exist. The error thrown is

PDOException

SQLSTATE[42P01]: Undefined table: 7 ERROR: relation "abstracttaskoperatorhistoryrecord_id_seq" does not exist
Originally created by @DemoniacDeath on GitHub (Sep 1, 2023). ### BC Break Report | Q | A |------------ | ------ | BC Break | yes | Version | 2.16.0 #### Summary Persisting an `@Entity` which has it's `@Id` defined in it's parent (`@MappedSuperclass`) with `IDENTITY` generator strategy on PostgreSQL platform. It seems that the breaking change occured in this PR https://github.com/doctrine/orm/pull/10455, specifically changes in `ClassMetadataFactory` that removed the condtion of `$rootEntityFound` for calling `inheritIdGeneratorMapping()` #### Previous behavior In version `2.15.5` the `IdentityGenerator` used to retrieve inserted id came from the Entity itself with `$sequenceName` matching the schema. So the value was retrieved from the correct sequence. #### Current behavior Now the `IdentityGenerator` used to retrieve inserted id comes from the parent `@MappedSuperclass` and `$sequenceName` is wrong (name of parent class + sequence suffix) and non-existent in schema, so a PostgreSQL error about relation not existing is thrown. #### How to reproduce E.g. (irrelevant code and domain-specific terminology is ommited): ```php /** * @ORM\MappedSuperclass */ abstract class AbstractTaskOperatorHistoryRecord { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="IDENTITY") */ private ?int $id = null; } ``` ```php /** * @ORM\Entity() * @ORM\Table(name="foo_bar_task_operator_history") */ class FooBarTaskOperatorHistoryRecord extends AbstractTaskOperatorHistoryRecord { } ``` ```php $this->_em->persist(new FooBarTaskOperatorHistoryRecord()); $this->_em->flush(); ``` The PostgreSQL sequence used by `id` column in `foo_bar_task_operator_history` table is `foo_bar_task_operator_history_id_seq`. The sequence used to retrieve inserted id was `foo_bar_task_operator_history_id_seq` before the breaking change. Now it is trying to get the value from `abstracttaskoperatorhistoryrecord_id_seq` which obviously does not and should not exist. The error thrown is ``` PDOException SQLSTATE[42P01]: Undefined table: 7 ERROR: relation "abstracttaskoperatorhistoryrecord_id_seq" does not exist ```
admin added the Bug label 2026-01-22 15:47:08 +01:00
admin closed this issue 2026-01-22 15:47:08 +01:00
Author
Owner

@pkly commented on GitHub (Sep 6, 2023):

I've noticed a similar issue in one of our projects, running on MariaDB, similarly with a parent entity (DiscriminatorMap + Joined inheritance) but in our case I see:

An exception occurred while executing a query: SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens

2.15.5 works as expected, 2.16.0 crashes on the same code.
We have a parent entity with a generated id value, the child entity also contains an automatically generated uuid value (via custom generator).

@pkly commented on GitHub (Sep 6, 2023): I've noticed a similar issue in one of our projects, running on MariaDB, similarly with a parent entity (DiscriminatorMap + Joined inheritance) but in our case I see: ``` An exception occurred while executing a query: SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens ``` 2.15.5 works as expected, 2.16.0 crashes on the same code. We have a parent entity with a generated id value, the child entity also contains an automatically generated uuid value (via custom generator).
Author
Owner

@ghost commented on GitHub (Oct 11, 2023):

Hello I found a workaround which consists of changing the parent's id to protected instead of private

@ghost commented on GitHub (Oct 11, 2023): Hello I found a workaround which consists of changing the parent's id to protected instead of private
Author
Owner

@DemoniacDeath commented on GitHub (Oct 11, 2023):

Hello I found a workaround which consists of changing the parent's id to protected instead of private

changing id property from private to protected did not change the result of failing test case https://github.com/doctrine/orm/pull/10928 for me

@DemoniacDeath commented on GitHub (Oct 11, 2023): > Hello I found a workaround which consists of changing the parent's id to protected instead of private changing id property from private to protected did not change the result of failing test case https://github.com/doctrine/orm/pull/10928 for me
Author
Owner

@mpdude commented on GitHub (Oct 12, 2023):

@DemoniacDeath did you opt into the new „report fields where declared“ setting?

@mpdude commented on GitHub (Oct 12, 2023): @DemoniacDeath did you opt into the new „report fields where declared“ setting?
Author
Owner

@DemoniacDeath commented on GitHub (Oct 13, 2023):

@mpdude

OrmFunctionalTestCase.php:803

        $config->setMetadataDriverImpl(
            $mappingDriver ?? ORMSetup::createDefaultAnnotationDriver([
                realpath(__DIR__ . '/Models/Cache'),
                realpath(__DIR__ . '/Models/GeoNames'),
            ], null, true)
        );

it is set to true in the failing test case (by default). changing it to false did not change the outcome.

AFAIUI the issue is specifically with the code in \Doctrine\ORM\Mapping\ClassMetadataFactory::doLoadMetadata

@DemoniacDeath commented on GitHub (Oct 13, 2023): @mpdude `OrmFunctionalTestCase.php:803` ```php $config->setMetadataDriverImpl( $mappingDriver ?? ORMSetup::createDefaultAnnotationDriver([ realpath(__DIR__ . '/Models/Cache'), realpath(__DIR__ . '/Models/GeoNames'), ], null, true) ); ``` it is set to true in the failing test case (by default). changing it to `false` did not change the outcome. AFAIUI the issue is specifically with the code in `\Doctrine\ORM\Mapping\ClassMetadataFactory::doLoadMetadata`
Author
Owner

@melroy89 commented on GitHub (Oct 27, 2023):

Mbin is also effected by this regression issue of Doctrine: https://github.com/MbinOrg/mbin/pull/147

@melroy89 commented on GitHub (Oct 27, 2023): Mbin is also effected by this regression issue of Doctrine: https://github.com/MbinOrg/mbin/pull/147
Author
Owner

@mpdude commented on GitHub (Nov 6, 2023):

I see two ways how this could be fixed.

The first one is in #11050. It reverts the way inheritance works for @SequenceGeneratorDefinition and mapped superclasses. But, with this change one cannot define @SequenceGeneratorDefinition on mapped superclasses and expect it to be inherited by child entity classes - at least not when using the new reportFieldsWhereDeclared mapping driver mode.

The second approach is in #11052. It will recognize when an explicit @SequenceGeneratorDefinition is used and pass that one on unchanged to child classes (= all use the same sequence name). Otherwise, the default one will be generated for every single class, so the sequence name is based on the table name.

@mpdude commented on GitHub (Nov 6, 2023): I see two ways how this could be fixed. The first one is in #11050. It reverts the way inheritance works for `@SequenceGeneratorDefinition` and mapped superclasses. But, with this change one cannot define `@SequenceGeneratorDefinition` on mapped superclasses and expect it to be inherited by child entity classes - at least not when using the new `reportFieldsWhereDeclared` mapping driver mode. The second approach is in #11052. It will recognize when an explicit `@SequenceGeneratorDefinition` is used and pass that one on unchanged to child classes (= all use the same sequence name). Otherwise, the default one will be generated for every single class, so the sequence name is based on the table name.
Author
Owner

@Chris53897 commented on GitHub (Nov 7, 2023):

@mpdude I have tested your solution. Thanks for your work. Looks like it works for me with 2.16.2 and report_fields_where_declared: true

@DemoniacDeath can you confirm this too?

@Chris53897 commented on GitHub (Nov 7, 2023): @mpdude I have tested your solution. Thanks for your work. Looks like it works for me with 2.16.2 and `report_fields_where_declared: true` @DemoniacDeath can you confirm this too?
Author
Owner

@mpdude commented on GitHub (Nov 7, 2023):

Please also test #11052.

@mpdude commented on GitHub (Nov 7, 2023): Please also test #11052.
Author
Owner

@DemoniacDeath commented on GitHub (Nov 8, 2023):

@Chris53897 @mpdude indeed, both solutions have resolved the issue

@DemoniacDeath commented on GitHub (Nov 8, 2023): @Chris53897 @mpdude indeed, both solutions have resolved the issue
Author
Owner

@Arkemlar commented on GitHub (Dec 22, 2023):

Same problem here but in my case I use Embeddable that contains id field:


#[ORM\Embeddable]
class AutoincrementId extends BaseId implements Stringable
{
    #[ORM\Id, ORM\GeneratedValue(strategy: 'IDENTITY'), ORM\Column(type: 'bigint', nullable: false, options: ['unsigned' => true])]
    protected int $id;
}


#[ORM\Entity]
class ProductUnit
{
    #[ORM\Embedded(columnPrefix: false)]
    public readonly ProductUnitId $id;
}

It worked fine with strategy "AUTO", but with strategy "IDENTITY" it throws exception:
SQLSTATE[42P01]: Undefined table: 7 ERROR: relation "product_unit_id_id_seq" does not exist CONTEXT: unnamed portal parameter $1 = '...'

Identity strategy so fucked, wasted lots of hours trying to make it work...

@Arkemlar commented on GitHub (Dec 22, 2023): Same problem here but in my case I use Embeddable that contains id field: ```php #[ORM\Embeddable] class AutoincrementId extends BaseId implements Stringable { #[ORM\Id, ORM\GeneratedValue(strategy: 'IDENTITY'), ORM\Column(type: 'bigint', nullable: false, options: ['unsigned' => true])] protected int $id; } #[ORM\Entity] class ProductUnit { #[ORM\Embedded(columnPrefix: false)] public readonly ProductUnitId $id; } ``` It worked fine with strategy "AUTO", but with strategy "IDENTITY" it throws exception: `SQLSTATE[42P01]: Undefined table: 7 ERROR: relation "product_unit_id_id_seq" does not exist CONTEXT: unnamed portal parameter $1 = '...'` Identity strategy so fucked, wasted lots of hours trying to make it work...
Author
Owner

@greg0ire commented on GitHub (Dec 22, 2023):

@Arkemlar please stay polite, no one wants to hear you complain like that, especially not people providing you with free software .

@greg0ire commented on GitHub (Dec 22, 2023): @Arkemlar please stay polite, no one wants to hear you complain like that, especially not people providing you with free software .
Author
Owner

@Arkemlar commented on GitHub (Dec 22, 2023):

@greg0ire I didn't mean anyone in person or group of people at all, only Identity strategy feature because it makes a lot of pain working with it.

@Arkemlar commented on GitHub (Dec 22, 2023): @greg0ire I didn't mean anyone in person or group of people at all, only Identity strategy feature because it makes a lot of pain working with it.
Author
Owner

@mpdude commented on GitHub (Dec 22, 2023):

@Arkemlar do #11050 or #11052 fix your issue as well?

@mpdude commented on GitHub (Dec 22, 2023): @Arkemlar do #11050 or #11052 fix your issue as well?
Author
Owner

@Arkemlar commented on GitHub (Dec 22, 2023):

@mpdude I tried both, but none fixed my issue :(
Doctrine generates incorrect sequence name product_unit_id_id_seq, while it must be product_unit_id_seq (doctrine:migration:diff generated name is product_unit_id_id_seq). I don't get how is it possible that migrations work properly while runtime not. I suppose it must use same mappings...

The error bumps up when doctrine's BigIntegerIdentityGenerator tries to get the id of freshly inserted row, see screenshot:

Debug panel screenshot

изображение

@Arkemlar commented on GitHub (Dec 22, 2023): @mpdude I tried both, but none fixed my issue :( Doctrine generates incorrect sequence name `product_unit_id_id_seq`, while it must be `product_unit_id_seq` (`doctrine:migration:diff` generated name is `product_unit_id_id_seq`). I don't get how is it possible that migrations work properly while runtime not. I suppose it must use same mappings... The error bumps up when doctrine's `BigIntegerIdentityGenerator` tries to get the id of freshly inserted row, see screenshot: <details> <summary>Debug panel screenshot</summary> ![изображение](https://github.com/doctrine/orm/assets/19671202/bf6568c5-eca0-4553-89be-a05dc2b697d9) </details>
Author
Owner

@mpdude commented on GitHub (Dec 22, 2023):

@Arkemlar does your code work with, say, 2.14.x, but broke at or after 2.15.1?

If not (does not work before either) you're seeing an issue different from this one here.

@mpdude commented on GitHub (Dec 22, 2023): @Arkemlar does your code work with, say, 2.14.x, but broke at or after 2.15.1? If not (does not work before either) you're seeing an issue different from this one here.
Author
Owner

@Arkemlar commented on GitHub (Dec 22, 2023):

@mpdude nope, it is not working in any version unless I use custom id generator that removes sequenceName argument at all. But my problem is similar to topic starter's issue because it caused by invalid sequence name.

@Arkemlar commented on GitHub (Dec 22, 2023): @mpdude nope, it is not working in any version unless I use custom id generator that removes sequenceName argument at all. But my problem is similar to topic starter's issue because it caused by invalid sequence name.
Author
Owner

@mpdude commented on GitHub (Dec 22, 2023):

Please create a different issue then so we can keep the discussion here focused.

@mpdude commented on GitHub (Dec 22, 2023): Please create a different issue then so we can keep the discussion here focused.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#7216