mirror of
https://github.com/doctrine/orm.git
synced 2026-03-23 22:42:18 +01:00
DDC-1389: Querying subclass entities using DQL results in broken SQL being generated #1740
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @doctrinebot on GitHub (Sep 22, 2011).
Originally assigned to: @guilhermeblanco on GitHub.
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 commented on GitHub (Sep 25, 2011):
Comment created by @beberlei:
Assigned to guilherme
@doctrinebot commented on GitHub (Oct 13, 2011):
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 commented on GitHub (Oct 14, 2011):
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.namefield somewhereAs a workaround I copy/pasted all parent fields (+getters/setters) in the child class and removed the inheritance
@doctrinebot commented on GitHub (Oct 16, 2011):
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 commented on GitHub (Oct 16, 2011):
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 commented on GitHub (Oct 16, 2011):
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 commented on GitHub (Oct 16, 2011):
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?
There is only a single place where this can happen in "walkFromClause" and it handles this correctly:
@doctrinebot commented on GitHub (Oct 16, 2011):
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
If I use that definition template renderings always fails this way:
Line 36 of that template is:
Relevant PDO stacktrace:
Definition of the parent Tag class
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 commented on GitHub (Oct 16, 2011):
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 commented on GitHub (Oct 16, 2011):
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 commented on GitHub (Oct 16, 2011):
Comment created by marcoalbarelli:
There you go:
I just added the
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 commented on GitHub (Oct 16, 2011):
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 commented on GitHub (Oct 16, 2011):
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: class_parents(): 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 commented on GitHub (Oct 16, 2011):
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.vatTotal_dbID AS vatTotal_dbID7, 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:
And the entity class definition for GrandInvoiceTotal:
Here is the mapped superclass "DataObject":
@doctrinebot commented on GitHub (Oct 16, 2011):
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 commented on GitHub (Oct 16, 2011):
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 commented on GitHub (Oct 16, 2011):
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 commented on GitHub (Oct 16, 2011):
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 commented on GitHub (Oct 16, 2011):
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 commented on GitHub (Oct 16, 2011):
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 commented on GitHub (Oct 16, 2011):
Issue was closed with resolution "Invalid"
@doctrinebot commented on GitHub (Oct 31, 2011):
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).