Using nonInsertable/nonUpdateable columns with JOINED inheritance type triggers SQL error #6923

Closed
opened 2026-01-22 15:41:26 +01:00 by admin · 6 comments
Owner

Originally created by @bordgab on GitHub (Feb 3, 2022).

Bug report

JoinedSubclassPersister::getInsertColumnList() does not take care of notInsertable/notUpdateable property of field mapping in parent class, therefore the column(s) appears in INSERT/UPDATE statement.

This wrong behaviour generates a DriverException with the following message, because BasicEntityPersister::prepareUpdateData() (correctly) does not provide the appropriate changeset:

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

ORM version: 2.11

Follow up to https://github.com/doctrine/orm/pull/9118

Originally created by @bordgab on GitHub (Feb 3, 2022). ### Bug report `JoinedSubclassPersister::getInsertColumnList()` does not take care of `notInsertable`/`notUpdateable` property of field mapping in parent class, therefore the column(s) appears in INSERT/UPDATE statement. This wrong behaviour generates a `DriverException` with the following message, because `BasicEntityPersister::prepareUpdateData()` (correctly) does not provide the appropriate changeset: >An exception occurred while executing a query: SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens **ORM version: 2.11** Follow up to https://github.com/doctrine/orm/pull/9118
admin added the Bug label 2026-01-22 15:41:26 +01:00
admin closed this issue 2026-01-22 15:41:26 +01:00
Author
Owner

@derrabus commented on GitHub (Feb 4, 2022):

ORM 2.9 is not maintained anymore. Can you please try to reproduce your problem with a recent release? If this issue still occurs, please provide steps to reproduce your problem.

@derrabus commented on GitHub (Feb 4, 2022): ORM 2.9 is not maintained anymore. Can you please try to reproduce your problem with a recent release? If this issue still occurs, please provide steps to reproduce your problem.
Author
Owner

@beberlei commented on GitHub (Feb 4, 2022):

This must be 2.11 since it uses a new feature

@beberlei commented on GitHub (Feb 4, 2022): This must be 2.11 since it uses a new feature
Author
Owner

@bordgab commented on GitHub (Feb 4, 2022):

This must be 2.11 since it uses a new feature

Thanks for the version fix, I overlooked the version number....

@bordgab commented on GitHub (Feb 4, 2022): > This must be 2.11 since it uses a new feature Thanks for the version fix, I overlooked the version number....
Author
Owner

@curry684 commented on GitHub (Oct 14, 2022):

I'm experiencing a related issue in ORM 2.13.3, I think it's strongly related based op superficial debugging.

I have the following field in an abstract entity used in single table inheritance:

    #[Column(type: Types::DATETIME_MUTABLE, insertable: false, updatable: false,
        columnDefinition: 'datetime GENERATED ALWAYS AS (DATE_ADD(Start, INTERVAL Hours * 60 MINUTE))',
        generated: 'ALWAYS')]
    private \DateTimeInterface $end;

The insertable property is properly processed, the field is not added to the INSERT when flushing. However the generated: 'ALWAYS' should trigger a SELECT right after, to fill the generated field. This is not happening, causing the field to remain uninitialized until the next request.

I suspect it's caused by the derived class not properly being aware that its base class contains the generated field.

@curry684 commented on GitHub (Oct 14, 2022): I'm experiencing a related issue in ORM 2.13.3, I think it's strongly related based op superficial debugging. I have the following field in an *abstract* entity used in *single table inheritance*: ``` #[Column(type: Types::DATETIME_MUTABLE, insertable: false, updatable: false, columnDefinition: 'datetime GENERATED ALWAYS AS (DATE_ADD(Start, INTERVAL Hours * 60 MINUTE))', generated: 'ALWAYS')] private \DateTimeInterface $end; ``` The `insertable` property is properly processed, the field is not added to the `INSERT` when flushing. However the `generated: 'ALWAYS'` should trigger a `SELECT` right after, to fill the generated field. This is not happening, causing the field to remain uninitialized until the next request. I suspect it's caused by the derived class not properly being aware that its base class contains the generated field.
Author
Owner

@hakai commented on GitHub (Feb 7, 2023):

I'm getting the same issue in doctrine-bundle:2.8.2 and orm 2.14.0, using a MySQL database. Apparently the code that picks up the parameters respects the insertable flags, but the code that creates the SQL query does not. Minimal example:

src/Entity/AbstractUser.php:

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'user_common')]
#[ORM\InheritanceType('JOINED')]
#[ORM\DiscriminatorColumn(name: 'entity', type: 'string')]
#[ORM\DiscriminatorMap(['customer' => 'Customer'])]
abstract class AbstractUser
{
    #[ORM\Id]
    #[ORM\Column(type: 'integer')]
    #[ORM\GeneratedValue]
    public int $id = 0;

    #[ORM\Column(type: 'datetime', insertable: false, updatable: false, options: ['default' => 'CURRENT_TIMESTAMP'])]
    public ?\DateTime $dateCreated = null;
}

src/Entity/Customer.php:

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Table(name: 'user_customers')]
#[ORM\Entity]
class Customer extends AbstractUser
{
    #[ORM\Column]
    public string $customerNumber = '42';
}

src/Controller/DebugController.php:

<?php

namespace App\Controller;

use App\Entity\Customer;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class DebugController extends AbstractController
{
    #[Route(path: '/debug', name: 'debug', methods: ['GET'])]
    public function debug(EntityManagerInterface $em): Response
    {
        $customer = new Customer();
        $em->persist($customer);
        $em->flush();
        dd($customer);
    }
}

Outcome after executing the route /debug (with dd($this->sql, $this->params); in vendor/doctrine/dbal/src/Statement.php::execute($params)@187):

INSERT INTO user_common (date_created, entity) VALUES (?, ?)
array:1 [ 1 => "customer" ]

Expected outcome:

INSERT INTO user_common ( entity) VALUES (?)
array:1 [ 1 => "customer" ]

EDIT: The updatable flag (at least in my tests) worked as expected. It will cause an empty transaction if you only change the value of the updatable:false field, but that is acceptable imo.

@hakai commented on GitHub (Feb 7, 2023): I'm getting the same issue in doctrine-bundle:2.8.2 and orm 2.14.0, using a MySQL database. Apparently the code that picks up the parameters respects the insertable flags, but the code that creates the SQL query does not. Minimal example: src/Entity/AbstractUser.php: ``` <?php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity] #[ORM\Table(name: 'user_common')] #[ORM\InheritanceType('JOINED')] #[ORM\DiscriminatorColumn(name: 'entity', type: 'string')] #[ORM\DiscriminatorMap(['customer' => 'Customer'])] abstract class AbstractUser { #[ORM\Id] #[ORM\Column(type: 'integer')] #[ORM\GeneratedValue] public int $id = 0; #[ORM\Column(type: 'datetime', insertable: false, updatable: false, options: ['default' => 'CURRENT_TIMESTAMP'])] public ?\DateTime $dateCreated = null; } ``` src/Entity/Customer.php: ``` <?php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; #[ORM\Table(name: 'user_customers')] #[ORM\Entity] class Customer extends AbstractUser { #[ORM\Column] public string $customerNumber = '42'; } ``` src/Controller/DebugController.php: ``` <?php namespace App\Controller; use App\Entity\Customer; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class DebugController extends AbstractController { #[Route(path: '/debug', name: 'debug', methods: ['GET'])] public function debug(EntityManagerInterface $em): Response { $customer = new Customer(); $em->persist($customer); $em->flush(); dd($customer); } } ``` Outcome after executing the route /debug (with ``dd($this->sql, $this->params);`` in ``vendor/doctrine/dbal/src/Statement.php::execute($params)@187``): ``` INSERT INTO user_common (date_created, entity) VALUES (?, ?) array:1 [ 1 => "customer" ] ``` Expected outcome: ``` INSERT INTO user_common ( entity) VALUES (?) array:1 [ 1 => "customer" ] ``` EDIT: The updatable flag (at least in my tests) worked as expected. It will cause an empty transaction if you only change the value of the updatable:false field, but that is acceptable imo.
Author
Owner

@mrVrAlex commented on GitHub (Aug 22, 2023):

@derrabus You can close this due to https://github.com/doctrine/orm/pull/10598
Now (in v2.16.x) this should be working as expected.

@mrVrAlex commented on GitHub (Aug 22, 2023): @derrabus You can close this due to https://github.com/doctrine/orm/pull/10598 Now (in v2.16.x) this should be working as expected.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#6923