Skip to content

Loading…

DDC-1389: Querying subclass entities using DQL results in broken SQL being generated #2009

Closed
doctrinebot opened this Issue · 22 comments

2 participants

@doctrinebot

Jira issue originally created by user dalvarez:

For entities that are instances of an entity class that inherits from another entity class, querying them directly using DQL will generate a broken SQL statement in which a table alias is referenced but never declared.

Here is an example SQL query:

SELECT a from \A a where a.someField = 1

Assume that A is an entity class that inherits from another entity class, say B.

The generated query will then be of the following (wrong) form:

SELECT i0.someField as someField0, a1_.someOtherfield AS someOtherfield1 FROM A a1_ WHERE a1.someField = 1 LIMIT 1

You can see that the generated query references two table aliases in the select list (i0* and a1_), but only declares one (a1*). This can't possibly work. Apparently the inheritance join does not make it into SQL. As inheritance joins are documented to happen transparently, I believe this is unintended behaviour.

The error message is then, of course, something like this:

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'i0_.somefield' in 'field list'' ...

Strangely, it also happens for fields that are replicated to the subclass, e. g. the primary key.

Querying entity classes that do not inherit from other entity classes works as expected though, including the base classes in an entity inheritance hierarchy.

@doctrinebot

Comment created by @beberlei:

Assigned to guilherme

@doctrinebot

Comment created by dalvarez:

Here is a little incentive:

I will sponsor 200 € to an account of Ben's choice, if this issue gets fixed before Sunday morning, October, 16th, 2011, 12 o'clock a. m. (GMT).

The award will be paid immediately after job completion and provision of a patched 2.1.2 version.

@doctrinebot

Comment created by marcoalbarelli:

This bug affected also me:
I had a Tag class and a Category class extending the Tag
The only field in the Category class was a "parent" OneToMany of Category class

the ->findAll() method worked
while instead the find($id) method raised the exception about missing table reference only if in twig I had the category.name field somewhere

As a workaround I copy/pasted all parent fields (+getters/setters) in the child class and removed the inheritance

An exception has been thrown during the rendering of a template ("SQLSTATE[42P01]: Undefined table: 7 ERROR: missing FROM-clause entry for table "t0"
LINE 1: ...t1.parent*id AS parent*id7 FROM Category t1 WHERE t0.id = $1
^")

class definition: 
/****
 * Category
 *
 * @ORM\Table()
 */
class Category extends Tag{
    /****
     * @var Category $parent
     * @ORM\ManyToOne(targetEntity="Category")
     */
    private $parent;
....
@doctrinebot

Comment created by @beberlei:

This is fixed in master already, i am digging out the patch from the history, Guilherme committed that while i was in holidays the last weeks.

@doctrinebot

Comment created by @beberlei:

Are you sure your query has a WHERE condition? There is a bug where this happens when no WHERE condition is specified.

@doctrinebot

Comment created by @beberlei:

@Marco: Your mapping is wrong you are missing the @ORM\Entity annotation. Can you show the class level docblock of the class Tag?

@doctrinebot

Comment created by @beberlei:

@Daniel: I think your mapping is wrong as well, or the ClassMetadata of your "A" lost the information that its Joined Inheritance. Can you verify?

echo $em->getClassMetadata('A')->isInheritanceTypeJoined();

There is only a single place where this can happen in "walkFromClause" and it handles this correctly:

    public function walkFromClause($fromClause)
    {
        $identificationVarDecls = $fromClause->identificationVariableDeclarations;
        $sqlParts = array();

        foreach ($identificationVarDecls as $identificationVariableDecl) {
            $sql = '';

            $rangeDecl = $identificationVariableDecl->rangeVariableDeclaration;
            $dqlAlias = $rangeDecl->aliasIdentificationVariable;

            $this->_rootAliases[] = $dqlAlias;

            $class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName);
            $sql .= $class->getQuotedTableName($this->_platform) . ' '
                  . $this->getSQLTableAlias($class->table['name'], $dqlAlias);

            if ($class->isInheritanceTypeJoined()) {
                $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
            }

            foreach ($identificationVariableDecl->joinVariableDeclarations as $joinVarDecl) {
                $sql .= $this->walkJoinVariableDeclaration($joinVarDecl);
            }

            if ($identificationVariableDecl->indexBy) {
                $this->_rsm->addIndexBy(
                    $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable,
                    $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field
                );
            }

            $sqlParts[] = $this->*platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK*MODE));
        }

        return ' FROM ' . implode(', ', $sqlParts);
    }
@doctrinebot

Comment created by marcoalbarelli:

@Benjamin:
Sry, I didn't think it was important and stripped it from the pasted version I put in the comment
The @Entity is there and points to the repository
Here is the dump of the entire class as it was

/****
 * @ORM\Table()
 * @ORM\HasLifecycleCallbacks()
 * @ORM\Entity(repositoryClass="SomeBundle\Entity\CategoryRepository")
 */
class Category etends Tag{

    /****
     * @var Category $parent
     * @ORM\ManyToOne(targetEntity="Category")
     */
    private $parent;

    public function getParent() {
        return $this->parent;
    }

    public function setParent($parent) {
        $this->parent = $parent;
    }

}

If I use that definition template renderings always fails this way:

 Twig*Error*Runtime: An exception has been thrown during the rendering of a template 
("SQLSTATE[42P01]: Undefined table: 7 ERROR: missing FROM-clause entry for table "t0"<br />
LINE 1: ...t1.parent*id AS parent*id7 FROM Category t1 WHERE t0.id = $1<br /> ^") 
in "SomeBundle:Post:index.html.twig" at line 36.

Line 36 of that template is:

<td>` entity.category.name|default("<i>not set</i>")|raw  `</td>

Relevant PDO stacktrace:

at Connection ->executeQuery ('SELECT t1.id AS id2, t1.name AS name3, 
t1.slug AS slug4, t1.createdAt AS createdat5, t1.description AS description6, 
t1.parent*id AS parent*id7 
FROM Category t1 WHERE t0.id = ?', array('4'), array('1')) 
in /home/marco/somefolder/Symfony/vendor/doctrine/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php at line 569 

Definition of the parent Tag class

 * @ORM\Table()
 * @ORM\HasLifecycleCallbacks()
 * @ORM\Entity(repositoryClass="SomeBundle\Entity\TagRepository")
 */
class Tag implements Serializable
{
yadda yadda ...

I didn't specify any explicit inheritanceType nor any discriminatorMapping since I thought Doctrine would ahve taken care of those details with its own defaults, right?

P.S.
I just did a bin/vendors update and the problem is still there

@doctrinebot

Comment created by @beberlei:

@Marco: You are missing the @ORM\InheritanceType and @ORM\DiscriminatorMap properties, see the documentation on Inheritance:

http://www.doctrine-project.org/docs/orm/2.0/en/reference/inheritance-mapping.html

This is probably a good point to make the error messages better.

@doctrinebot

Comment created by @beberlei:

Hm you probably have this proerpties. Can you give me a full show of the Tag class aswell? I need to reproduce this.

The problem is that all the unit-tests with examples of this work perfectly. So there has to be a special case somewhere that we have overlooked so far.

@doctrinebot

Comment created by marcoalbarelli:

There you go:

<?php

namespace SomeBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use \DateTime;
use \Serializable;
use SomeBundle\Entity\Tag;
use SomeBundle\Util\TextSanifier;

/****
 * Promobit\CMS\SiteBundle\Entity\Tag
 *
 * @ORM\Table()
 * @ORM\HasLifecycleCallbacks()
 * @ORM\Entity(repositoryClass="SomeBundle\Entity\TagRepository")
 */
class Tag implements Serializable
{
    /****
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /****
     * @var string $name
     *
     * @ORM\Column(name="name", type="string", length=255)
     */
    private $name;

    /****
     * @var string $slug
     *
     * @ORM\Column(name="slug", type="string", length=255)
     */
    private $slug;

    /****
     * @var datetime $createdAt
     *
     * @ORM\Column(name="createdAt", type="datetime")
     */
    private $createdAt;

    /****
     * @var string $descritpion
     *
     * @ORM\Column(name="description", type="string",length="255")
     */
    private $description;

    public function **construct() {
        $this->createdAt = new DateTime();
        $this->description = '';
    }


    /****
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /****
     * Set name
     *
     * @param string $name
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /****
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /****
     * Set createdAt
     *
     * @param datetime $createdAt
     */
    public function setCreatedAt($createdAt)
    {
        $this->createdAt = $createdAt;
    }

    /****
     * Get createdAt
     *
     * @return datetime
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }


    public function getSlug() {
        return $this->slug;
    }

    public function setSlug($slug) {
        $this->slug = $slug;
    }

    public function getDescription() {
        return $this->description;
    }

    public function setDescription($description) {
        $this->description = $description;
    }


    /****
     * @ORM\PrePersist
     * @ORM\PreUpdate
     */
    public function updateSlug()
    {
        $this->slug = TextSanifier::slugify($this->name);
    }


    public function serialize(){
        return serialize(array(
            $this->id,
            $this->name,
            $this->slug,
            $this->createdAt,
            $this->description
        ));

    }

    public function unserialize($serialized) {
        list(
            $this->id,
            $this->name,
            $this->slug,
            $this->createdAt,
            $this->description
                ) = unserialize($serialized);
    }

    public function **toString(){
        return $this->name;
    }

}

I just added the

 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="discr", type="string")
 * @ORM\DiscriminatorMap({"tag" = "Tag", "category" = "Category"})

annotation and it works, even though I'm not very happy about this approach since this way I have to hardcode inheritance information in the parent class

I saw the documentation, but as I said before I thought Doctrine would have filled in the blanks on its own, as it does in many other repects (table and column names for one)
Sry for misunderstanding the documentation and partially wasting your time

P.S.
I solved my issue by creating a BaseTag entity with @MappedSuperclass annotation and then extending that one
Seems like a decent solution for me
Thx all

@doctrinebot

Comment created by dalvarez:

I will quickly prepare the actual data to give you a complete picture, and come back to you in a couple of minutes.

@doctrinebot

Comment created by dalvarez:

There was an occassion of a missing DiscriminatorMap in my case, too, though at a different place that I originally referred to when posting the issue. There was nothing that would have hinted me at that... Some basic validation would be helpful here.

After fixing that, the problem now manifests in PHP warnings and a subsequent fatal error.

Warning: classparents(): object or string expected in /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Mapping/ClassMetadataFactory.php on line 224 Warning: array_reverse() expects parameter 1 to be array, boolean given in /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Mapping/ClassMetadataFactory.php on line 224 Warning: Invalid argument supplied for foreach() in /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Mapping/ClassMetadataFactory.php on line 224 Fatal error: Uncaught exception 'ReflectionException' with message 'Class does not exist' in /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Mapping/ClassMetadata.php:66 Stack trace: #0 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Mapping/ClassMetadata.php(66): ReflectionClass->__construct('') #1 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Mapping/ClassMetadataFactory.php(364): Doctrine\ORM\Mapping\ClassMetadata->_construct(NULL) #2 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Mapping/ClassMetadataFactory.php(265): Doctrine\ORM\Mapping\ClassMetadataFactory->newClassMetadataInstance(NULL) #3 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Mapping/ClassMetadataFactory.php(170): Doctrine\ORM\Mapping\ClassMetadataFactory->loadMetadata(NULL) #4 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/EntityManager.php(257): Doctrine\ORM\Mapping\ClassMetadataFactory->getMetadataFor(NULL) # in /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Mapping/ClassMetadata.php on line 66

This is probably unrelated to the original issue, but it is equally blocking my use case.

I debugged the entity name that gets passed to the constructor as an argument. The class that gets passed to the ORM\Mapping\ClassMetadata constructor is an Entity that inherits from a mapped superclass and implements an interface. It does not have any other parents except for the mapped superclass though. Any ideas on this?

I could debug it further and try to break this down to a more isolated case, too, if necessary.

@doctrinebot

Comment created by dalvarez:

From the stack trace it can be seen, that ORM\EntityManager->getClassMetadata() gets called with a NULL argument.

Unfortunately, the stack trace ends there. I have thrown an exception from that point to complete it (don't worry about the exception, I willingly threw it, but it illustrates the call graph):

Fatal error: Uncaught exception 'Exception' in /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/EntityManager.php:263 Stack trace: #0 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/UnitOfWork.php(1862): Doctrine\ORM\EntityManager->getClassMetadata(NULL) #1 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php(139): Doctrine\ORM\UnitOfWork->createEntity(NULL, Array, Array) #2 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php(44): Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator->hydrateRow(Array, Array, Array) #3 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php(99): Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator->hydrateAll() #4 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Persisters/BasicEntityPersister.php(580): Doctrine\ORM\Internal\Hydration\AbstractHy in /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/EntityManager.php on line 263

Here is the statement that gets hydrated at that point:

SELECT t1.totalPurchaseValueRegularVATRate AS totalPurchaseValueRegularVATRate2, t1.totalPurchaseValueReducedVATRate AS totalPurchaseValueReducedVATRate3, t1.sum AS sum4, t1.dbID AS dbID5, t0.grandTotal AS grandTotal6, t0.vatTotaldbID AS vatTotaldbID7, t1.doctrineTypeDiscriminator FROM GrandInvoiceTotal t0 INNER JOIN InvoiceTotal t1 ON t0.dbID = t1.dbID WHERE t1.dbID = ?

Here is the Entity class definition for InvoiceTotal:

<?php 

namespace persistentData\model\core\invoiceCreator;

use \persistentData\model\DataObject;
use \persistentData\model\core\Total;


/****
 * @Entity
 * @InheritanceType("JOINED")
 * @DiscriminatorColumn(name="doctrineTypeDiscriminator", type="string", length=64)
 * @DiscriminatorMap({"invoiceTotal"          = "persistentData\model\core\invoiceCreator\InvoiceTotal",
 *                    "grandInvoiceTotal"     = "persistentData\model\core\invoiceCreator\GrandInvoiceTotal"})
 */

class InvoiceTotal extends DataObject implements Total {

   /****
    * @Column(type="decimal", precision=16, scale=8)
    */

   protected $totalPurchaseValueRegularVATRate;


   /****
    * @Column(type="decimal", precision=16, scale=8, nullable=true)
    */

   protected $totalPurchaseValueReducedVATRate;


   /****
    * @Column(type="decimal", precision=16, scale=8)
    */

   protected $sum;


   public function **construct($totalPurchaseValueRegularVATRate, $totalPurchaseValueReducedVATRate = null) {

      $this->totalPurchaseValueRegularVATRate    = $totalPurchaseValueRegularVATRate;

      $this->totalPurchaseValueReducedVATRate    = $totalPurchaseValueReducedVATRate;

      $this->sum                                 = $totalPurchaseValueRegularVATRate <ins> $totalPurchaseValueReducedVATRate;
   }


   public function getTotalPurchaseValueRegularVATRate() {

      return $this->totalPurchaseValueRegularVATRate;
   }


   public function getTotalPurchaseValueReducedVATRate() {

      return $this->totalPurchaseValueReducedVATRate;
   }
}

?>

And the entity class definition for GrandInvoiceTotal:

<?php 

namespace persistentData\model\core\invoiceCreator;

use \persistentData\model\core\GrandTotal;


/****
 * @Entity
 */

class GrandInvoiceTotal extends InvoiceTotal implements GrandTotal {

   /****
    * @OneToOne(targetEntity="persistentData\model\core\invoiceCreator\InvoiceTotal", cascade={"persist", "remove", "detach"})
    * @JoinColumn(name="vatTotal_dbID", referencedColumnName="dbID")
    */

   protected $vatTotal;


   /****
    * @Column(type="decimal", precision=16, scale=8)
    */

   protected $grandTotal;


   public function **construct(InvoiceTotal $vatTotal, $totalPurchaseValueRegularVATRate, $totalPurchaseValueReducedVATRate) {

      parent::**construct($totalPurchaseValueRegularVATRate, $totalPurchaseValueReducedVATRate);

      $this->vatTotal      = $vatTotal;

      $this->grandTotal    = $this->sum </ins> $vatTotal->sum;
   }


   public function getGrandTotal() {

      return $this->grandTotal;
   }


   public function getVATTotalRegularVATRate() {

      return $this->vatTotal->totalPurchaseValueRegularVATRate;
   }


   public function getVATTotalReducedVATRate() {

      return $this->vatTotal->totalPurchaseValueReducedVATRate;
   }
}

?>

Here is the mapped superclass "DataObject":

<?php 

namespace persistentData\model;


/****
 * @MappedSuperclass
 */

abstract class DataObject {

   /****
    * @Id
    * @Column(type="bigint")
    * @GeneratedValue
    */

   protected $dbID;


   /****
    * Accessor method for Doctrine proxies to hook in and lazy-load the data object.
    * This provides a simple record with accessible properties.
    */

   public function & **get($name) {

      $referenceHolderVar = $this->$name;

      return $referenceHolderVar;
   }


   /****
    * Mutator method for Doctrine proxies to hook in and lazy-load the data object.
    * This provides a simple record with accessible properties.
    */

   public function **set($name, $value) {

      $this->$name = $value;
   }


   /****
    * **isset method for Doctrine proxies to hook in and lazy-load the data object.
    * This provides a simple record with accessible properties.
    */

   public function **isset($name) {

      return isset ($this->$name);
   }


   /****
    * **unset method for Doctrine proxies to hook in and lazy-load the data object.
    * This provides a simple record with accessible properties.
    */

   public function **unset($name) {

      unset ($this->$name);
   }
}
@doctrinebot

Comment created by dalvarez:

What I am ultimately doing at that point, is calling EntityManager::remove() on an entity, which performs a cascading delete, which in turn deletes another entity, which in turn deletes another entity, and so on, up to the point where it starts to delete instances of InvoiceTotal and GrandInvoiceTotal.

I just do not know what to do at this point because all of this is so involved into the inner workings of Doctrine 2. To me it seems that the cascading delete somehow entangles itself, somehow ending up with a read statement that it cannot process properly, because it cannot find some metadata it requires. Since the Discriminator Map is in place, what else could be missing?

Of course, I have regenerated the proxies and updated the database, to reflect the latest changes.

@doctrinebot

Comment created by dalvarez:

I believe the original bug simply consisted in non-existent validation, leading to the possibility broken SQL being generated. But even with the Discriminator Map present where it was previously missing, I still cannot perform a cascading delete due to the crashing problem described above.

I am still sticking to my incentive of sponsoring 200 €, if my use case gets working today, which is basically performing a cascading delete on the data shown above. Right now the problem has unfortunately just shifted.

@doctrinebot

Comment created by dalvarez:

I can provide you with additional sourcecode at any time, if you require it, or live contact and access to a testing system.

@doctrinebot

Comment created by dalvarez:

Here is a complete stack trace:

#0 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Mapping/ClassMetadataFactory.php(364): Doctrine\ORM\Mapping\ClassMetadata->construct(NULL)
#1 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Mapping/ClassMetadataFactory.php(265): Doctrine\ORM\Mapping\ClassMetadataFactory->newClassMetadataInstance(NULL)
#2 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Mapping/ClassMetadataFactory.php(170): Doctrine\ORM\Mapping\ClassMetadataFactory->loadMetadata(NULL)
#3 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/EntityManager.php(257): Doctrine\ORM\Mapping\ClassMetadataFactory->getMetadataFor(NULL)
#4 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/UnitOfWork.php(1862): Doctrine\ORM\EntityManager->getClassMetadata(NULL)
#5 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php(139): Doctrine\ORM\UnitOfWork->createEntity(NULL, Array, Array)
#6 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php(44): Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator->_hydrateRow(Array, Array, Array)
#7 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php(99): Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator->_hydrateAll()
#8 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/Persisters/BasicEntityPersister.php(581): Doctrine\ORM\Internal\Hydration\AbstractHydrator->hydrateAll(Object(Doctrine\DBAL\Driver\PDOStatement), Object(Doctrine\ORM\Query\ResultSetMapping), Array)
#9 /var/www/invoiceCreator/src/persistentData/doctrine/proxies/persistentDatamodelcoreinvoiceCreatorGrandInvoiceTotalProxy.php(23): Doctrine\ORM\Persisters\BasicEntityPersister->load(Array, Object(persistentData\doctrine\proxies\persistentDatamodelcoreinvoiceCreatorGrandInvoiceTotalProxy))
#10 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/UnitOfWork.php(1718): persistentData\doctrine\proxies\persistentDatamodelcoreinvoiceCreatorGrandInvoiceTotalProxy->
load()
#11 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/UnitOfWork.php(1303): Doctrine\ORM\UnitOfWork->cascadeRemove(Object(persistentData\doctrine\proxies\persistentDatamodelcoreinvoiceCreatorGrandInvoiceTotalProxy), Array)
#12 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/UnitOfWork.php(1728): Doctrine\ORM\UnitOfWork->doRemove(Object(persistentData\doctrine\proxies\persistentDatamodelcoreinvoiceCreatorGrandInvoiceTotalProxy), Array)
#13 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/UnitOfWork.php(1303): Doctrine\ORM\UnitOfWork->cascadeRemove(Object(persistentData\model\core\invoiceCreator\Invoice), Array)
#14 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/UnitOfWork.php(1725): Doctrine\ORM\UnitOfWork->doRemove(Object(persistentData\model\core\invoiceCreator\Invoice), Array)
#15 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/UnitOfWork.php(1303): Doctrine\ORM\UnitOfWork->cascadeRemove(Object(persistentData\doctrine\proxies\persistentDatamodelcoreinvoiceCreatorInvoiceCreatorResultProxy), Array)
#16 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/UnitOfWork.php(1728): Doctrine\ORM\UnitOfWork->doRemove(Object(persistentData\doctrine\proxies\persistentDatamodelcoreinvoiceCreatorInvoiceCreatorResultProxy), Array)
#17 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/UnitOfWork.php(1303): Doctrine\ORM\UnitOfWork->cascadeRemove(Object(persistentData\model\core\Run), Array)
#18 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/UnitOfWork.php(1279): Doctrine\ORM\UnitOfWork->doRemove(Object(persistentData\model\core\Run), Array)
#19 /var/www/invoiceCreator/src/thirdPartyComponents/doctrine/Doctrine/ORM/EntityManager.php(481): Doctrine\ORM\UnitOfWork->remove(Object(persistentData\model\core\Run))
#20 /var/www/invoiceCreator/src/persistentData/DB.php(212): Doctrine\ORM\EntityManager->remove(Object(persistentData\model\core\Run))
#21 /var/www/invoiceCreator/src/frontend/mainPage/MainPageActions.php(50): persistentData\DB::delete(Object(persistentData\model\core\Run))
#22 /var/www/invoiceCreator/src/frontend/mainPage/MainPageAdapter.php(87): frontend\mainPage\MainPageActions::deleteRun('23')
#23 /var/www/invoiceCreator/documentRoot/admin/index.php(10): frontend\mainPage\MainPageAdapter::handleRequest()
#24 {main}

@doctrinebot

Comment created by dalvarez:

Now I found the root cause. In the database, the doctrineTypeDiscriminator column was not initialized.

Apparently, after adding the discriminator map to the entity class InvoiceTotal, the schema-tool:update command added the column, but never initialized it.

This means that if a discriminator map is added to already existing entities, all instances existing in the database must be manually initialized with discriminator values. This is a classic pain in the arse, but thinking about it, it is logical, because the update command has nothing to do with data, it just manipulates the database schema. So conceptually, it is clean. I'll post another update once it finally works, because right now I the cascading delete still runs into an integrity constraint violation error.

@doctrinebot

Comment created by dalvarez:

Up to now I have not been able to reproduce the integrity constraint violation in an isolated test case.

It is there, reliably reproducible, but only in the context of the application. I know which constraint fails, and when, but not why. It should not. The only possibility is that things get deleted in the wrong order.

I will close this as invalid, because anyway, technically it is not the original issue. I will go on to write a manual delete routine to avoid the cascading delete. Maybe on the way I will find out what exactly caused the integrity constraint violation.

@doctrinebot

Issue was closed with resolution "Invalid"

@doctrinebot

Comment created by @beberlei:

Added validation for this inside the hydrators. Hydration will now fail if a discriminator value is an empty string (0 is still allowed).

@doctrinebot doctrinebot closed this
@doctrinebot doctrinebot added the Bug label
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.