Closed nicktacular closed 9 years ago
I dig a bit and the second query you run returns "stale" results because documents from result set are already managed by UoW and since ->refresh()
wasn't added to QueryBuilder it did not hydrated new results into document but just returned already managed ones.
As a solution in ODM itself Query could set refresh
hint by itself if it fetches all fields but I'm not sure if it's good idea in all cases
I just spent a couple of hours coming to the same conclusion as this post, so this is definitely not expected behaviour.
If this is seen as correct, even if non-intuitive, should a note be added to the documentation where ->multi(true) is discussed to describe why this happens?
@malarzm Setting the refresh
hint by default is not a good idea, at least I don't think it is. Only findAndUpdate()
supports the returnNew()
functionality, which is used to return the new object instead of the old object (which is the default behavior IIRC). So setting the refresh
hint by default would result in an extra trip to the database along with (possibly unnecessary) document hydration. I think it's better to let the developer decide when to refresh objects. I do think however that this should be properly documented in the database: Updating documents using the query builder does not refresh objects managed by the document manager.
@alcaeus thanks for giving a con for this, won't consider it anymore :)
How would you set the refresh hint?
@nicktacular By calling refresh() in the query builder:
$query = $mgr->createQueryBuilder($docName); $query->refresh(true)->findAndUpdate()->field('something')->set('somethingElse');
Note that it only works when the query is a) returning data and b) hydration is enabled (i.e. no hydrate(false)
). An update()
query does not return data, only findAndUpdate
does (and only one).
Here's test case I've pulled together back then:
<?php
namespace Doctrine\ODM\MongoDB\Tests\Functional\Ticket;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
class GH880Test extends \Doctrine\ODM\MongoDB\Tests\BaseTest
{
public function test880()
{
$docs = array();
$docs[] = new GH880Document('hello', 1);
$docs[] = new GH880Document('world', 1);
foreach ($docs as $doc) {
$this->dm->persist($doc);
}
$this->dm->flush();
$query = $this->dm->createQueryBuilder(__NAMESPACE__ . '\GH880Document');
$cursor = $query->find()->getQuery()->execute();
foreach ($cursor as $c) {
$this->assertEquals(1, $c->category);
}
$query = $this->dm->createQueryBuilder(__NAMESPACE__ . '\GH880Document');
$query->update()
->multiple(true)
->field('category')->equals(1)
->field('category')->set(3)
->getQuery()
->execute();
$query = $this->dm->createQueryBuilder(__NAMESPACE__ . '\GH880Document');
// here ->refresh() was needed for the test to pass
$cursor = $query->find()->refresh()->getQuery()->execute();
foreach ($cursor as $c) {
$this->assertEquals(3, $c->category);
}
}
}
/** @ODM\Document */
class GH880Document
{
/** @ODM\Id */
public $id;
/** @ODM\String */
public $status;
/** @ODM\Int */
public $category;
public function __construct($status = "", $category = 0)
{
$this->status = $status;
$this->category = $category;
}
}
Retriaging this for documentation as I couldn't find any mention of refresh()
in Query Builder API and using that was fixing reported issue.
Ok, so the key is the refresh()
call in $query->find()->refresh()->getQuery()->execute()
to ensure that the data in that cursor is rehydrated from DB?
Thanks for your help.
@nicktacular yes, calling refresh()
ensures objects will be data fetched from database will be hydrated into objects
Note to self: let's add @malarzm's test case as a documentation example.
This has been document in #1203. Additionally, I added @malarzm's test case.
Summary
When using
multiple()
in conjunction withupdate()
followed by an immediateflush()
, the documents affected by the update are not properly updated and still reflect the old values.Details
I've written a script to help debug this problem and to show the proof of this issue at play. You'll notice that I'm using
doctrine/mongodb-odm
through composer and bootstrapping from within ZF2.The script basically does these things:
status
andcategory
.multiple(true)
.If you go to the section with
Should see all the category=1 objects as category=3, however you won't due to this issue:
you'll notice that this occurs immediately after themultiple
update followed by a flush. You'd expect that the categories have all been updated to3
since that's the query that was just previously run. However, Doctrine doesn't pick up on the new data even though going directly to the DB shows that those values were updated.This doesn't seem like expected behavior, especially after a call to flush.
So what is the output of this gem?
And finally, for reference, here's my model: