Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added result caching for QueryBuilder #5539

Merged
merged 1 commit into from Jul 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/en/reference/query-builder.rst
Expand Up @@ -369,3 +369,25 @@ in your query as a return value:
->where('email = ' . $queryBuilder->createPositionalParameter($userInputEmail))
;
// SELECT id, name FROM users WHERE email = ?

Caching
-------

To use the result cache, it is necessary to call the method
``enableResultCache($cacheProfile)`` and pass a instance of
``Doctrine\DBAL\Cache\QueryCacheProfile`` with a cache lifetime
value in seconds. A cache key can optionally be added if needed.

.. code-block:: php

<?php

$queryBuilder
->select('id', 'name')
->from('users')
->enableResultCache(new QueryCacheProfile(300, 'some-key'))
;

.. note::

See the :ref:`Caching <caching>` section for more information.
56 changes: 46 additions & 10 deletions src/Query/QueryBuilder.php
Expand Up @@ -2,6 +2,7 @@

namespace Doctrine\DBAL\Query;

use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\ParameterType;
Expand Down Expand Up @@ -139,6 +140,11 @@ class QueryBuilder
*/
private int $boundCounter = 0;

/**
* The query cache profile used for caching results.
*/
private ?QueryCacheProfile $resultCacheProfile = null;

/**
* Initializes a new <tt>QueryBuilder</tt>.
*
Expand Down Expand Up @@ -227,7 +233,7 @@ public function getState()
*/
public function fetchAssociative()
{
return $this->connection->fetchAssociative($this->getSQL(), $this->params, $this->paramTypes);
return $this->executeQuery()->fetchAssociative();
}

/**
Expand All @@ -240,7 +246,7 @@ public function fetchAssociative()
*/
public function fetchNumeric()
{
return $this->connection->fetchNumeric($this->getSQL(), $this->params, $this->paramTypes);
return $this->executeQuery()->fetchNumeric();
}

/**
Expand All @@ -253,7 +259,7 @@ public function fetchNumeric()
*/
public function fetchOne()
{
return $this->connection->fetchOne($this->getSQL(), $this->params, $this->paramTypes);
return $this->executeQuery()->fetchOne();
}

/**
Expand All @@ -265,7 +271,7 @@ public function fetchOne()
*/
public function fetchAllNumeric(): array
{
return $this->connection->fetchAllNumeric($this->getSQL(), $this->params, $this->paramTypes);
return $this->executeQuery()->fetchAllNumeric();
}

/**
Expand All @@ -277,7 +283,7 @@ public function fetchAllNumeric(): array
*/
public function fetchAllAssociative(): array
{
return $this->connection->fetchAllAssociative($this->getSQL(), $this->params, $this->paramTypes);
return $this->executeQuery()->fetchAllAssociative();
}

/**
Expand All @@ -290,7 +296,7 @@ public function fetchAllAssociative(): array
*/
public function fetchAllKeyValue(): array
{
return $this->connection->fetchAllKeyValue($this->getSQL(), $this->params, $this->paramTypes);
return $this->executeQuery()->fetchAllKeyValue();
}

/**
Expand All @@ -304,7 +310,7 @@ public function fetchAllKeyValue(): array
*/
public function fetchAllAssociativeIndexed(): array
{
return $this->connection->fetchAllAssociativeIndexed($this->getSQL(), $this->params, $this->paramTypes);
return $this->executeQuery()->fetchAllAssociativeIndexed();
}

/**
Expand All @@ -316,7 +322,7 @@ public function fetchAllAssociativeIndexed(): array
*/
public function fetchFirstColumn(): array
{
return $this->connection->fetchFirstColumn($this->getSQL(), $this->params, $this->paramTypes);
return $this->executeQuery()->fetchFirstColumn();
}

/**
Expand All @@ -326,7 +332,12 @@ public function fetchFirstColumn(): array
*/
public function executeQuery(): Result
{
return $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes);
return $this->connection->executeQuery(
$this->getSQL(),
$this->params,
$this->paramTypes,
$this->resultCacheProfile
);
}

/**
Expand Down Expand Up @@ -361,7 +372,7 @@ public function execute()
'QueryBuilder::execute() is deprecated, use QueryBuilder::executeQuery() for SQL queries instead.'
);

return $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes);
return $this->executeQuery();
}

Deprecation::trigger(
Expand Down Expand Up @@ -1588,4 +1599,29 @@ public function __clone()
$this->params[$name] = clone $param;
}
}

/**
* Enables caching of the results of this query, for given amount of seconds
* and optionally specified witch key to use for the cache entry.
*
* @return $this
*/
public function enableResultCache(QueryCacheProfile $cacheProfile): self
{
$this->resultCacheProfile = $cacheProfile;

return $this;
}

/**
* Disables caching of the results of this query.
*
* @return $this
*/
public function disableResultCache(): self
{
$this->resultCacheProfile = null;

return $this;
}
}
111 changes: 94 additions & 17 deletions tests/Query/QueryBuilderTest.php
Expand Up @@ -2,6 +2,7 @@

namespace Doctrine\DBAL\Tests\Query;

use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
Expand Down Expand Up @@ -965,11 +966,16 @@ public function testFetchAssociative(
array $parameterTypes,
string $expectedSql
): void {
$qb = new QueryBuilder($this->conn);
$qb = new QueryBuilder($this->conn);
$mockedResult = $this->createMock(Result::class);

$this->conn->expects(self::once())
->method('executeQuery')
->with($expectedSql, $parameters, $parameterTypes, null)
->willReturn($mockedResult);

$mockedResult->expects(self::once())
->method('fetchAssociative')
->with($expectedSql, $parameters, $parameterTypes)
->willReturn(['username' => 'jwage', 'password' => 'changeme']);

$row = $qb->select($select)
Expand All @@ -995,11 +1001,16 @@ public function testFetchNumeric(
array $parameterTypes,
string $expectedSql
): void {
$qb = new QueryBuilder($this->conn);
$qb = new QueryBuilder($this->conn);
$mockedResult = $this->createMock(Result::class);

$this->conn->expects(self::once())
->method('executeQuery')
->with($expectedSql, $parameters, $parameterTypes, null)
->willReturn($mockedResult);

$mockedResult->expects(self::once())
->method('fetchNumeric')
->with($expectedSql, $parameters, $parameterTypes)
->willReturn(['jwage', 'changeme']);

$row = $qb->select($select)
Expand All @@ -1025,11 +1036,16 @@ public function testFetchOne(
array $parameterTypes,
string $expectedSql
): void {
$qb = new QueryBuilder($this->conn);
$qb = new QueryBuilder($this->conn);
$mockedResult = $this->createMock(Result::class);

$this->conn->expects(self::once())
->method('executeQuery')
->with($expectedSql, $parameters, $parameterTypes, null)
->willReturn($mockedResult);

$mockedResult->expects(self::once())
->method('fetchOne')
->with($expectedSql, $parameters, $parameterTypes)
->willReturn('jwage');

$username = $qb->select($select)
Expand All @@ -1055,11 +1071,16 @@ public function testFetchAllAssociative(
array $parameterTypes,
string $expectedSql
): void {
$qb = new QueryBuilder($this->conn);
$qb = new QueryBuilder($this->conn);
$mockedResult = $this->createMock(Result::class);

$this->conn->expects(self::once())
->method('executeQuery')
->with($expectedSql, $parameters, $parameterTypes, null)
->willReturn($mockedResult);

$mockedResult->expects(self::once())
->method('fetchAllAssociative')
->with($expectedSql, $parameters, $parameterTypes)
->willReturn(
[
['username' => 'jwage', 'password' => 'changeme'],
Expand Down Expand Up @@ -1096,11 +1117,16 @@ public function testFetchAllNumeric(
array $parameterTypes,
string $expectedSql
): void {
$qb = new QueryBuilder($this->conn);
$qb = new QueryBuilder($this->conn);
$mockedResult = $this->createMock(Result::class);

$this->conn->expects(self::once())
->method('executeQuery')
->with($expectedSql, $parameters, $parameterTypes, null)
->willReturn($mockedResult);

$mockedResult->expects(self::once())
->method('fetchAllNumeric')
->with($expectedSql, $parameters, $parameterTypes)
->willReturn(
[
['jwage', 'changeme'],
Expand Down Expand Up @@ -1137,11 +1163,16 @@ public function testFetchAllKeyValue(
array $parameterTypes,
string $expectedSql
): void {
$qb = new QueryBuilder($this->conn);
$qb = new QueryBuilder($this->conn);
$mockedResult = $this->createMock(Result::class);

$this->conn->expects(self::once())
->method('executeQuery')
->with($expectedSql, $parameters, $parameterTypes, null)
->willReturn($mockedResult);

$mockedResult->expects(self::once())
->method('fetchAllKeyValue')
->with($expectedSql, $parameters, $parameterTypes)
->willReturn(
[
'jwage' => 'changeme',
Expand Down Expand Up @@ -1178,11 +1209,16 @@ public function testFetchAllAssociativeIndexed(
array $parameterTypes,
string $expectedSql
): void {
$qb = new QueryBuilder($this->conn);
$qb = new QueryBuilder($this->conn);
$mockedResult = $this->createMock(Result::class);

$this->conn->expects(self::once())
->method('executeQuery')
->with($expectedSql, $parameters, $parameterTypes, null)
->willReturn($mockedResult);

$mockedResult->expects(self::once())
->method('fetchAllAssociativeIndexed')
->with($expectedSql, $parameters, $parameterTypes)
->willReturn(
[
1 => [
Expand Down Expand Up @@ -1223,11 +1259,16 @@ public function testFetchFirstColumn(
array $parameterTypes,
string $expectedSql
): void {
$qb = new QueryBuilder($this->conn);
$qb = new QueryBuilder($this->conn);
$mockedResult = $this->createMock(Result::class);

$this->conn->expects(self::once())
->method('executeQuery')
->with($expectedSql, $parameters, $parameterTypes, null)
->willReturn($mockedResult);

$mockedResult->expects(self::once())
->method('fetchFirstColumn')
->with($expectedSql, $parameters, $parameterTypes)
->willReturn(
[
'jwage',
Expand Down Expand Up @@ -1320,13 +1361,49 @@ public function testExecuteQuery(

$this->conn->expects(self::once())
->method('executeQuery')
->with($expectedSql, $parameters, $parameterTypes)
->with($expectedSql, $parameters, $parameterTypes, null)
->willReturn($mockedResult);

$results = $qb->select($select)
->from($from)
->where($where)
->setParameters($parameters, $parameterTypes)
->executeQuery();

self::assertSame(
$mockedResult,
$results
);
}

/**
* @param list<mixed>|array<string, mixed> $parameters
* @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $parameterTypes
*
* @dataProvider fetchProvider
*/
public function testExecuteQueryWithResultCaching(
string $select,
string $from,
string $where,
array $parameters,
array $parameterTypes,
string $expectedSql
): void {
$qb = new QueryBuilder($this->conn);
$qcp = new QueryCacheProfile(300);
$mockedResult = $this->createMock(Result::class);

$this->conn->expects(self::once())
->method('executeQuery')
->with($expectedSql, $parameters, $parameterTypes, $qcp)
->willReturn($mockedResult);

$results = $qb->select($select)
->from($from)
->where($where)
->setParameters($parameters, $parameterTypes)
->enableResultCache($qcp)
->executeQuery();

self::assertSame(
Expand Down