From 85a52d781efe8f03599900b206139b858fd23d55 Mon Sep 17 00:00:00 2001 From: bruno da silva Date: Tue, 11 Apr 2017 10:27:14 +0200 Subject: [PATCH 1/2] add a not weel writend/nammed test testing Query::expireResultCache() --- .../ORM/Functional/Ticket/GH2947Test.php | 95 +++++++++++++++++++ .../Doctrine/Tests/OrmFunctionalTestCase.php | 11 +++ 2 files changed, 106 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/GH2947Test.php diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH2947Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH2947Test.php new file mode 100644 index 000000000..0b21b57f0 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH2947Test.php @@ -0,0 +1,95 @@ +resultCacheImpl = new ArrayCache(); + + parent::setUp(); + + $this->_schemaTool->createSchema([$this->_em->getClassMetadata(GH2947Car::class)]); + } + + public function testIssue() + { + $this->createData(); + $initialQueryCount = $this->getCurrentQueryCount(); + + $query = $this->createQuery(); + self::assertEquals('BMW', (string) $query->getSingleResult()); + self::assertEquals($initialQueryCount + 1, $this->getCurrentQueryCount()); + + $this->updateData(); + self::assertEquals('BMW', (string) $query->getSingleResult()); + self::assertEquals($initialQueryCount + 2, $this->getCurrentQueryCount()); + + $query->expireResultCache(true); + self::assertEquals('Dacia', (string) $query->getSingleResult()); + self::assertEquals($initialQueryCount + 3, $this->getCurrentQueryCount()); + + $query->expireResultCache(false); + self::assertEquals('Dacia', (string) $query->getSingleResult()); + self::assertEquals($initialQueryCount + 3, $this->getCurrentQueryCount()); + } + + private function createQuery() + { + return $this->_em->createQueryBuilder() + ->select('car') + ->from(GH2947Car::class, 'car') + ->getQuery() + ->useResultCache(true, 3600, 'foo-cache-id'); + } + + private function createData() + { + $this->_em->persist(new GH2947Car('BMW')); + $this->_em->flush(); + $this->_em->clear(); + } + + private function updateData() + { + $this->_em->createQueryBuilder() + ->update(GH2947Car::class, 'car') + ->set('car.brand', ':newBrand') + ->where('car.brand = :oldBrand') + ->setParameter('newBrand', 'Dacia') + ->setParameter('oldBrand', 'BMW') + ->getQuery() + ->execute(); + } +} + +/** + * @Entity + * @Table(name="GH2947_car") + */ +class GH2947Car +{ + /** + * @Id + * @Column(type="string", length=25) + * @GeneratedValue(strategy="NONE") + */ + public $brand; + + public function __construct(string $brand) + { + $this->brand = $brand; + } + + public function __toString(): string + { + return $this->brand; + } +} diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 2ff2c347c..5716f2a50 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -68,6 +68,13 @@ abstract class OrmFunctionalTestCase extends OrmTestCase */ protected $_usedModelSets = []; + /** + * To be configured by the test that uses result set cache + * + * @var \Doctrine\Common\Cache\Cache|null + */ + protected $resultCacheImpl; + /** * Whether the database schema has already been created. * @@ -699,6 +706,10 @@ abstract class OrmFunctionalTestCase extends OrmTestCase $config->setProxyDir(__DIR__ . '/Proxies'); $config->setProxyNamespace('Doctrine\Tests\Proxies'); + if (null !== $this->resultCacheImpl) { + $config->setResultCacheImpl($this->resultCacheImpl); + } + $enableSecondLevelCache = getenv('ENABLE_SECOND_LEVEL_CACHE'); if ($this->isSecondLevelCacheEnabled || $enableSecondLevelCache) { From e71272e2b4e1f13318adfb1b16cc09c2e25ae70a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Cobucci?= Date: Sun, 30 Apr 2017 21:12:40 +0200 Subject: [PATCH 2/2] Evict result set cache if Query#expireResultCache() was called --- lib/Doctrine/ORM/Query.php | 19 +++++++++++ tests/Doctrine/Tests/ORM/Query/QueryTest.php | 33 ++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index 12fb3f560..9a5ded2c7 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -20,6 +20,7 @@ namespace Doctrine\ORM; use Doctrine\DBAL\LockMode; +use Doctrine\ORM\Query\Exec\AbstractSqlExecutor; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\ParserResult; use Doctrine\ORM\Query\QueryException; @@ -322,9 +323,27 @@ final class Query extends AbstractQuery list($sqlParams, $types) = $this->processParameterMappings($paramMappings); + $this->evictResultSetCache($executor, $sqlParams, $types); + return $executor->execute($this->_em->getConnection(), $sqlParams, $types); } + private function evictResultSetCache(AbstractSqlExecutor $executor, array $sqlParams, array $types) + { + if (null === $this->_queryCacheProfile || ! $this->getExpireResultCache()) { + return; + } + + $cacheDriver = $this->_queryCacheProfile->getResultCacheDriver(); + $statements = (array) $executor->getSqlStatements(); // Type casted since it can either be a string or an array + + foreach ($statements as $statement) { + $cacheKeys = $this->_queryCacheProfile->generateCacheKeys($statement, $sqlParams, $types); + + $cacheDriver->delete(reset($cacheKeys)); + } + } + /** * Evict entity cache region */ diff --git a/tests/Doctrine/Tests/ORM/Query/QueryTest.php b/tests/Doctrine/Tests/ORM/Query/QueryTest.php index 9b8f24d40..6b0091e9c 100644 --- a/tests/Doctrine/Tests/ORM/Query/QueryTest.php +++ b/tests/Doctrine/Tests/ORM/Query/QueryTest.php @@ -243,4 +243,37 @@ class QueryTest extends OrmTestCase $query->setHydrationCacheProfile(null); $this->assertNull($query->getHydrationCacheProfile()); } + + /** + * @group 2947 + */ + public function testResultCacheEviction() + { + $this->_em->getConfiguration()->setResultCacheImpl(new ArrayCache()); + + $query = $this->_em->createQuery("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u") + ->useResultCache(true); + + /** @var DriverConnectionMock $driverConnectionMock */ + $driverConnectionMock = $this->_em->getConnection() + ->getWrappedConnection(); + + $driverConnectionMock->setStatementMock(new StatementArrayMock([['id_0' => 1]])); + + // Performs the query and sets up the initial cache + self::assertCount(1, $query->getResult()); + + $driverConnectionMock->setStatementMock(new StatementArrayMock([['id_0' => 1], ['id_0' => 2]])); + + // Retrieves cached data since expire flag is false and we have a cached result set + self::assertCount(1, $query->getResult()); + + // Performs the query and caches the result set since expire flag is true + self::assertCount(2, $query->expireResultCache(true)->getResult()); + + $driverConnectionMock->setStatementMock(new StatementArrayMock([['id_0' => 1]])); + + // Retrieves cached data since expire flag is false and we have a cached result set + self::assertCount(2, $query->expireResultCache(false)->getResult()); + } }