DDC-2575: Hydration bug #3303

Closed
doctrinebot opened this Issue Jul 27, 2013 · 14 comments

2 participants

@doctrinebot

Jira issue originally created by user nbottarini:

I have the following class mappings:

class A
{
    /****
     * @Id
     * @Column(type="integer")
     * @GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /****
     * @Column(type="string", length=100, nullable=FALSE)
     */    
    protected $sampleField;

    /****
     * @OneToOne(targetEntity="B", mappedBy="aRelation")
     ****/     
    protected $bRelation;
}
class B
{
    /****
     * @Id
     * @OneToOne(targetEntity="A", inversedBy="bRelation")
     * @JoinColumn(name="a_id", referencedColumnName="id", nullable=FALSE, onDelete="CASCADE")
     */
    protected $aRelation;

    /****
     * @ManyToOne(targetEntity="C")
     * @JoinColumn(name="c_id", referencedColumnName="id", nullable=FALSE, onDelete="CASCADE")
     */
    protected $cRelation;

}
class C
{
    /****
     * @Id
     * @Column(type="integer")
     * @GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /****
     * @Column(type="string", length=100, nullable=FALSE)
     */    
    protected $sampleField;
}

Then I make the following query:

$qb = $em->createQueryBuilder();
$qb = $qb->select('a, b, c')
      ->from('A','a')
      ->leftJoin('a.bRelation', 'b')
      ->leftJoin('b.cRelation', 'c');

$result = $qb->getQuery()->getResult();

The result contains a collection of instances of class A with the a.bRelation field populated and the a.bRelation.cRelation field populated in all rows except the last one.

The problem is an hydration problem. The Parser constructs the select statement with the fields of class A, then the fields of class C and last the fields of class B. The hydrator don't work correctly because when it's hydrating class C it doesn't find the class B (because it appears last in the select statement).
I think the problem is because class B only contains associations. If i put an extra field (an string for example) in class B it works as expected.

@doctrinebot

Comment created by popy:

I have the same kind of bug : i have a OneToMany relations which stays to null if I request both entities in one query, and miss an entity if I preload the related entities in a second query. It seems to occur on the last entity of the list.

If I remember well the hydrator code, there's (in the hydratation loop) something like "If we find a new root entity (or maybe on each row, i'm not sure), link the entities we didn't link". Maybe this thing is not done AFTER the loop for remaining entities.

I'll try to dig again into the hydrator code tomorrow to check this hypothesis.

@doctrinebot

Comment created by popy:

This bug seems more severe :

I made a test on a query with 3 entities (root, root->a, root->b, a and b relations are OneToMany, so no connections on this side), and there's the process I witness :

  • First result row
    • The hydrator finds the linked entities before the root... and just does nothing (line 359)
    • The hydrator finds the root entity, and hydrate it
  • Other result rows
    • The hydrator finds the linked entities before the root... and associate them with the previously found root entity, which is the root entity fetched on the first row
    • The hydrator finds the root entity, so trash the previous, and hydrate (without related entities, as they were linked to previous root entity)

To finish, the last row has no related entities, as its related entities were given to the previous row.

@doctrinebot

Comment created by popy:

Bug confirmed in a small Symfony app and Doctrine 2.3. I managed to reproduce the bug with 3 entities :

  • A (id autoincrement)
  • B (id autoincrement)
  • Root (composite id a,b which are ManyToOne relations to A and B entities)

Can provide the app to ease things.

@doctrinebot

Comment created by popy:

Possible workaround : declaring integer fields as ID (with the same field name as relation fields) makes the thing working again (at the price of thoose two useless properties and a prePersist method to fill them with related entity ids)

@doctrinebot

Comment created by @beberlei:

First step here: Try to reproduce this issue with the given entities above in a Testcase

@doctrinebot

Comment created by karolhor:

I created test for this issue, but I can't reproduce id. My pull request is here #878

@doctrinebot

Comment created by popy:

You should maybe call $this->_em->clear() at the end of your setUp method.

I still have a Symfony Bundle reproducing the bug, how can i hand it to you ?

@doctrinebot

Comment created by @beberlei:

[~popy] you can create a branch of that symfony standard edition, and push it to a fork of symfony-standard on your Github account. Then you can comment a link to your branch on Github.

@doctrinebot

Comment created by karolhor:

After Popy's suggestion I added $this->_em->clear() and now I have failing test. My fault with this quick "everything is ok".

I tried to search what's happening in ObjectHydrator but something strange is going on in hydrateRowData method.

@doctrinebot

Comment created by popy:

Be carefull, headache come fast while reading this method :p

As far as I know, the problem could be solved if the hydrator started by hydrating the root entity first. Maybe.

@doctrinebot

Comment created by karolhor:

In select statement fields are in this order Root, B, A. Relations in my test are Root 1:1 A *:1 B.
Hydrator first gets Root data and next B. But here (line 407 in ObjectHydrator) it doesn't find A parent in resultPointers.

For now I don't have any idea how to add new logic for this.

@doctrinebot

Comment created by @doctrinebot:

A related Github Pull-Request [GH-878] was closed:
#878

@doctrinebot

Comment created by @guilhermeblanco:

As of 38b6838 this issue is now fixed.

@doctrinebot

Issue was closed with resolution "Fixed"

@doctrinebot doctrinebot added this to the 2.5 milestone Dec 6, 2015
@doctrinebot doctrinebot closed this Dec 6, 2015
@doctrinebot doctrinebot added the Bug label Dec 7, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment