From 04832e2789086a0ee88826b97a9e39076d5f84be Mon Sep 17 00:00:00 2001 From: beberlei Date: Wed, 10 Feb 2010 19:09:25 +0000 Subject: [PATCH] [2.0] DDC-125 - Query Hints are now included in both QueryCache and ResultCache - QueryCache now also uses firstResult and maxResults for the cache key - ResultCache was fixed to use "getSql()" instead of "getDql()" --- lib/Doctrine/ORM/AbstractQuery.php | 8 +- lib/Doctrine/ORM/Query.php | 23 +++- .../Tests/ORM/Functional/QueryCacheTest.php | 103 ++++++++++++++---- .../Tests/ORM/Functional/ResultCacheTest.php | 64 ++++++++++- 4 files changed, 165 insertions(+), 33 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 460bf12a0..fd1766923 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -488,7 +488,7 @@ abstract class AbstractQuery // Check result cache if ($this->_useResultCache && $cacheDriver = $this->getResultCacheDriver()) { - $id = $this->getResultCacheId($params); + $id = $this->_getResultCacheId($params); $cached = $this->_expireResultCache ? false : $cacheDriver->fetch($id); if ($cached === false) { @@ -541,12 +541,14 @@ abstract class AbstractQuery * @param array $params * @return string $id */ - public function getResultCacheId(array $params) + protected function _getResultCacheId(array $params) { if ($this->_resultCacheId) { return $this->_resultCacheId; } else { - return md5($this->getDql() . var_export($params, true)); + $sql = $this->getSql(); + ksort($this->_hints); + return md5(implode(";", (array)$sql) . var_export($params, true) . var_export($this->_hints, true)); } } diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index 44fb0b800..5a4bc9d3b 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -173,11 +173,7 @@ final class Query extends AbstractQuery { // Check query cache if ($queryCache = $this->getQueryCacheDriver()) { - // Calculate hash for dql query. - // TODO: Probably need to include query hints in hash calculation, because query hints - // can have influence on the SQL. - // TODO: Include _maxResults and _firstResult in hash calculation - $hash = md5($this->getDql() . 'DOCTRINE_QUERY_CACHE_SALT'); + $hash = $this->_getQueryCacheId(); $cached = ($this->_expireQueryCache) ? false : $queryCache->fetch($hash); if ($cached === false) { @@ -438,4 +434,21 @@ final class Query extends AbstractQuery $this->setHint(self::HINT_INTERNAL_ITERATION, true); return parent::iterate($params, $hydrationMode); } + + /** + * Generate a cache id for the query cache - reusing the Result-Cache-Id generator. + * + * The query cache + * + * @return string + */ + protected function _getQueryCacheId() + { + ksort($this->_hints); + + return md5( + $this->getDql() . var_export($this->_hints, true) . + 'firstResult='.$this->_firstResult.'&maxResult='.$this->_maxResults.'DOCTRINE_QUERY_CACHE_SALT' + ); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php index e7971cf9b..504db0abe 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php @@ -19,38 +19,101 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase parent::setUp(); } - public function testQueryCache() + public function testQueryCache_DependsOnHints() + { + $query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux'); + + $cache = new ArrayCache(); + $query->setQueryCacheDriver($cache); + + $query->getResult(); + $this->assertEquals(1, count($cache->getIds())); + + $query->setHint('foo', 'bar'); + + $query->getResult(); + $this->assertEquals(2, count($cache->getIds())); + + return $query; + } + + /** + * @param $query + * @depends testQueryCache_DependsOnHints + */ + public function testQueryCache_DependsOnFirstResult($query) + { + $cache = $query->getQueryCacheDriver(); + $cacheCount = count($cache->getIds()); + + $query->setFirstResult(10); + + $query->getResult(); + $this->assertEquals($cacheCount + 1, count($cache->getIds())); + } + + /** + * @param $query + * @depends testQueryCache_DependsOnHints + */ + public function testQueryCache_DependsOnMaxResults($query) + { + $cache = $query->getQueryCacheDriver(); + $cacheCount = count($cache->getIds()); + + $query->setMaxResults(10); + + $query->getResult(); + $this->assertEquals($cacheCount + 1, count($cache->getIds())); + } + + public function testQueryCache_NoHitSaveParserResult() { $this->_em->getConfiguration()->setQueryCacheImpl(null); - $user = new CmsUser; - $user->name = 'Roman'; - $user->username = 'romanb'; - $user->status = 'dev'; - $this->_em->persist($user); - $this->_em->flush(); - - $query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux'); - $cache = new ArrayCache; + + $cache = $this->getMock('Doctrine\Common\Cache\AbstractCache', array('_doFetch', '_doContains', '_doSave', '_doDelete', 'getIds')); + $cache->expects($this->at(0)) + ->method('_doFetch') + ->with($this->isType('string')) + ->will($this->returnValue(false)); + $cache->expects($this->at(1)) + ->method('_doSave') + ->with($this->isType('string'), $this->isInstanceOf('Doctrine\ORM\Query\ParserResult'), $this->equalTo(null)); + $query->setQueryCacheDriver($cache); $users = $query->getResult(); + } - $this->assertTrue($cache->contains(md5('select ux from Doctrine\Tests\Models\CMS\CmsUser uxDOCTRINE_QUERY_CACHE_SALT'))); - $this->assertEquals(1, count($users)); - $this->assertEquals('Roman', $users[0]->name); + public function testQueryCache_HitDoesNotSaveParserResult() + { + $this->_em->getConfiguration()->setQueryCacheImpl(null); - $this->_em->clear(); + $query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux'); - $query2 = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux'); - $query2->setQueryCacheDriver($cache); + $sqlExecMock = $this->getMock('Doctrine\ORM\Query\Exec\AbstractSqlExecutor', array('execute')); + $sqlExecMock->expects($this->once()) + ->method('execute') + ->will($this->returnValue( 10 )); - $users = $query2->getResult(); + $parserResultMock = $this->getMock('Doctrine\ORM\Query\ParserResult'); + $parserResultMock->expects($this->once()) + ->method('getSqlExecutor') + ->will($this->returnValue($sqlExecMock)); - $this->assertTrue($cache->contains(md5('select ux from Doctrine\Tests\Models\CMS\CmsUser uxDOCTRINE_QUERY_CACHE_SALT'))); - $this->assertEquals(1, count($users)); - $this->assertEquals('Roman', $users[0]->name); + $cache = $this->getMock('Doctrine\Common\Cache\AbstractCache', array('_doFetch', '_doContains', '_doSave', '_doDelete', 'getIds')); + $cache->expects($this->once()) + ->method('_doFetch') + ->with($this->isType('string')) + ->will($this->returnValue($parserResultMock)); + $cache->expects($this->never()) + ->method('_doSave'); + + $query->setQueryCacheDriver($cache); + + $users = $query->getResult(); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php index 7e4a919a5..cfd1e39f5 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php @@ -30,23 +30,27 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase $query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux'); - $cache = new ArrayCache; - $query->setResultCacheDriver($cache); + + $cache = new ArrayCache(); + + $query->setResultCacheDriver($cache)->setResultCacheId('my_cache_id'); + + $this->assertFalse($cache->contains('my_cache_id')); $users = $query->getResult(); - $this->assertTrue($cache->contains($query->getResultCacheId(array()))); + $this->assertTrue($cache->contains('my_cache_id')); $this->assertEquals(1, count($users)); $this->assertEquals('Roman', $users[0]->name); $this->_em->clear(); $query2 = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux'); - $query2->setResultCacheDriver($cache); + $query2->setResultCacheDriver($cache)->setResultCacheId('my_cache_id'); $users = $query2->getResult(); - $this->assertTrue($cache->contains($query->getResultCacheId(array()))); + $this->assertTrue($cache->contains('my_cache_id')); $this->assertEquals(1, count($users)); $this->assertEquals('Roman', $users[0]->name); } @@ -58,6 +62,9 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase $query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux'); $query->setResultCacheDriver($cache); $query->setResultCacheId('testing_result_cache_id'); + + $this->assertFalse($cache->contains('testing_result_cache_id')); + $users = $query->getResult(); $this->assertTrue($cache->contains('testing_result_cache_id')); @@ -77,4 +84,51 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->getConfiguration()->setResultCacheImpl(null); } + + public function testNativeQueryResultCaching() + { + $rsm = new \Doctrine\ORM\Query\ResultSetMapping(); + $rsm->addScalarResult('id', 'u'); + $query = $this->_em->createNativeQuery('select u.id FROM cms_users u WHERE u.id = ?', $rsm); + $query->setParameter(1, 10); + + $cache = new ArrayCache(); + $query->setResultCacheDriver($cache)->useResultCache(true); + + $this->assertEquals(0, count($cache->getIds())); + $query->getResult(); + $this->assertEquals(1, count($cache->getIds())); + + return $query; + } + + /** + * @param $query + * @depends testNativeQueryResultCaching + */ + public function testResultCacheDependsOnQueryHints($query) + { + $cache = $query->getResultCacheDriver(); + $cacheCount = count($cache->getIds()); + + $query->setHint('foo', 'bar'); + $query->getResult(); + + $this->assertEquals($cacheCount + 1, count($cache->getIds())); + } + + /** + * @param $query + * @depends testNativeQueryResultCaching + */ + public function testResultCacheDependsOnParameters($query) + { + $cache = $query->getResultCacheDriver(); + $cacheCount = count($cache->getIds()); + + $query->setParameter(1, 50); + $query->getResult(); + + $this->assertEquals($cacheCount + 1, count($cache->getIds())); + } } \ No newline at end of file