From 5bd6ed5684551ba6a6d8c1d77d4cd16554ddcb0b Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Sat, 10 Feb 2018 14:10:14 +0200 Subject: [PATCH] Fixed issues in accidental merge of unfinished #15398 --- framework/db/ActiveQuery.php | 10 ++-- framework/db/CacheableQueryTrait.php | 63 ------------------------- framework/db/Command.php | 38 ++++++++++++++- framework/db/Query.php | 70 ++++++++++++++++++++++++---- tests/framework/db/QueryTest.php | 54 +++++++++++++++++++++ 5 files changed, 156 insertions(+), 79 deletions(-) delete mode 100644 framework/db/CacheableQueryTrait.php diff --git a/framework/db/ActiveQuery.php b/framework/db/ActiveQuery.php index 7dd264b395..225ab0758b 100644 --- a/framework/db/ActiveQuery.php +++ b/framework/db/ActiveQuery.php @@ -320,9 +320,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface } $command = $db->createCommand($sql, $params); - if ($this->hasCache()) { - $command->cache($this->queryCacheDuration, $this->queryCacheDependency); - } + $this->setCommandCache($command); + return $command; } @@ -345,9 +344,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface ->from(['c' => "({$this->sql})"]) ->params($this->params) ->createCommand($db); - if ($this->hasCache()) { - $command->cache($this->queryCacheDuration, $this->queryCacheDependency); - } + $this->setCommandCache($command); + return $command->queryScalar(); } diff --git a/framework/db/CacheableQueryTrait.php b/framework/db/CacheableQueryTrait.php deleted file mode 100644 index 52d0cdf08a..0000000000 --- a/framework/db/CacheableQueryTrait.php +++ /dev/null @@ -1,63 +0,0 @@ - - * @author Dmytro Naumenko - * @since 2.0.14 - */ -trait CacheableQueryTrait -{ - /** - * @var int the default number of seconds that query results can remain valid in cache. - * Use 0 to indicate that the cached data will never expire. And use a negative number to indicate - * query cache should not be used. - * @see cache() - */ - public $queryCacheDuration; - /** - * @var \yii\caching\Dependency the dependency to be associated with the cached query result for this command - * @see cache() - */ - public $queryCacheDependency; - - /** - * Enables query cache for this command or query. - * @param int $duration the number of seconds that query result of this command or query can remain valid in the cache. - * If this is not set, the value of [[Connection::queryCacheDuration]] will be used instead. - * Use 0 to indicate that the cached data will never expire. - * @param \yii\caching\Dependency $dependency the cache dependency associated with the cached result. - * @return $this the command or query object itself - */ - public function cache($duration = null, $dependency = null) - { - $this->queryCacheDuration = $duration !== null ? $duration : $this->db->queryCacheDuration; - $this->queryCacheDependency = $dependency; - return $this; - } - - /** - * Disables query cache for this command or query. - * @return $this the command or query object itself - */ - public function noCache() - { - $this->queryCacheDuration = -1; - return $this; - } - - /** - * Checks, whether caching is enabled - * @return bool - */ - public function hasCache() - { - return $this->queryCacheDuration !== null || $this->queryCacheDependency !== null; - } -} diff --git a/framework/db/Command.php b/framework/db/Command.php index 7320d4d682..810bd4a845 100644 --- a/framework/db/Command.php +++ b/framework/db/Command.php @@ -56,8 +56,6 @@ use yii\base\NotSupportedException; */ class Command extends Component { - use CacheableQueryTrait; - /** * @var Connection the DB connection that this command is associated with */ @@ -77,6 +75,18 @@ class Command extends Component * and is used to generate [[rawSql]]. Do not modify it directly. */ public $params = []; + /** + * @var int the default number of seconds that query results can remain valid in cache. + * Use 0 to indicate that the cached data will never expire. And use a negative number to indicate + * query cache should not be used. + * @see cache() + */ + public $queryCacheDuration; + /** + * @var \yii\caching\Dependency the dependency to be associated with the cached query result for this command + * @see cache() + */ + public $queryCacheDependency; /** * @var array pending parameters to be bound to the current PDO statement. @@ -101,6 +111,30 @@ class Command extends Component */ private $_retryHandler; + /** + * Enables query cache for this command. + * @param int $duration the number of seconds that query result of this command can remain valid in the cache. + * If this is not set, the value of [[Connection::queryCacheDuration]] will be used instead. + * Use 0 to indicate that the cached data will never expire. + * @param \yii\caching\Dependency $dependency the cache dependency associated with the cached query result. + * @return $this the command object itself + */ + public function cache($duration = null, $dependency = null) + { + $this->queryCacheDuration = $duration === null ? $this->db->queryCacheDuration : $duration; + $this->queryCacheDependency = $dependency; + return $this; + } + + /** + * Disables query cache for this command. + * @return $this the command object itself + */ + public function noCache() + { + $this->queryCacheDuration = -1; + return $this; + } /** * Returns the SQL statement for this command. diff --git a/framework/db/Query.php b/framework/db/Query.php index 2d17f54da3..b3391568b6 100644 --- a/framework/db/Query.php +++ b/framework/db/Query.php @@ -50,7 +50,6 @@ use yii\base\InvalidParamException; class Query extends Component implements QueryInterface { use QueryTrait; - use CacheableQueryTrait; /** * @var array the columns being selected. For example, `['id', 'name']`. @@ -115,7 +114,19 @@ class Query extends Component implements QueryInterface * For example, `[':name' => 'Dan', ':age' => 31]`. */ public $params = []; - + /** + * @var int|true the default number of seconds that query results can remain valid in cache. + * Use 0 to indicate that the cached data will never expire. + * Use a negative number to indicate that query cache should not be used. + * Use boolean `true` to indicate that [[Connection::queryCacheDuration]] should be used. + * @see cache() + */ + public $queryCacheDuration; + /** + * @var \yii\caching\Dependency the dependency to be associated with the cached query result for this query + * @see cache() + */ + public $queryCacheDependency; /** * Creates a DB command that can be used to execute this query. @@ -131,9 +142,8 @@ class Query extends Component implements QueryInterface list($sql, $params) = $db->getQueryBuilder()->build($this); $command = $db->createCommand($sql, $params); - if ($this->hasCache()) { - $command->cache($this->queryCacheDuration, $this->queryCacheDependency); - } + $this->setCommandCache($command); + return $command; } @@ -458,9 +468,8 @@ class Query extends Component implements QueryInterface ->select([$selectExpression]) ->from(['c' => $this]) ->createCommand($db); - if ($this->hasCache()) { - $command->cache($this->queryCacheDuration, $this->queryCacheDependency); - } + $this->setCommandCache($command); + return $command->queryScalar(); } @@ -1211,6 +1220,51 @@ PATTERN; return $this; } + /** + * Enables query cache for this Query. + * @param int|true the number of seconds that query results can remain valid in cache. + * Use 0 to indicate that the cached data will never expire. + * Use a negative number to indicate that query cache should not be used. + * Use boolean `true` to indicate that [[Connection::queryCacheDuration]] should be used. + * Defaults to `true`. + * @param \yii\caching\Dependency $dependency the cache dependency associated with the cached result. + * @return $this the Query object itself + */ + public function cache($duration = true, $dependency = null) + { + $this->queryCacheDuration = $duration; + $this->queryCacheDependency = $dependency; + return $this; + } + + /** + * Disables query cache for this Query. + * @return $this the Query object itself + * @since 2.0.14 + */ + public function noCache() + { + $this->queryCacheDuration = -1; + return $this; + } + + /** + * Sets $command cache, if this query has enabled caching. + * + * @param Command $command + * @return Command + * @since 2.0.14 + */ + protected function setCommandCache($command) + { + if ($this->queryCacheDuration !== null || $this->queryCacheDependency !== null) { + $duration = $this->queryCacheDuration === true ? null : $this->queryCacheDuration; + $command->cache($duration, $this->queryCacheDependency); + } + + return $command; + } + /** * Creates a new Query object and copies its property values from an existing one. * The properties being copies are the ones to be used by query builders. diff --git a/tests/framework/db/QueryTest.php b/tests/framework/db/QueryTest.php index 2ac1440a10..61d850a48a 100644 --- a/tests/framework/db/QueryTest.php +++ b/tests/framework/db/QueryTest.php @@ -7,6 +7,7 @@ namespace yiiunit\framework\db; +use yii\caching\ArrayCache; use yii\db\Connection; use yii\db\Expression; use yii\db\Query; @@ -621,4 +622,57 @@ abstract class QueryTest extends DatabaseTestCase $result = $query->one($db); $this->assertEquals('user3', $result['name']); } + + public function testQueryCache() + { + $db = $this->getConnection(); + $db->enableQueryCache = true; + $db->queryCache = new ArrayCache(); + $query = (new Query()) + ->select(['name']) + ->from('customer'); + $update = $db->createCommand('UPDATE {{customer}} SET [[name]] = :name WHERE [[id]] = :id'); + + $this->assertEquals('user1', $query->where(['id' => 1])->scalar($db), 'Asserting initial value'); + + // No cache + $update->bindValues([':id' => 1, ':name' => 'user11'])->execute(); + $this->assertEquals('user11', $query->where(['id' => 1])->scalar($db), 'Query reflects DB changes when caching is disabled'); + + // Connection cache + $db->cache(function (Connection $db) use ($query, $update) { + $this->assertEquals('user2', $query->where(['id' => 2])->scalar($db), 'Asserting initial value for user #2'); + + $update->bindValues([':id' => 2, ':name' => 'user22'])->execute(); + $this->assertEquals('user2', $query->where(['id' => 2])->scalar($db), 'Query does NOT reflect DB changes when wrapped in connection caching'); + + $db->noCache(function () use ($query, $db) { + $this->assertEquals('user22', $query->where(['id' => 2])->scalar($db), 'Query reflects DB changes when wrapped in connection caching and noCache simultaneously'); + }); + + $this->assertEquals('user2', $query->where(['id' => 2])->scalar($db), 'Cache does not get changes after getting newer data from DB in noCache block.'); + }, 10); + + + $db->enableQueryCache = false; + $db->cache(function ($db) use ($query, $update) { + $this->assertEquals('user22', $query->where(['id' => 2])->scalar($db), 'When cache is disabled for the whole connection, Query inside cache block does not get cached'); + $update->bindValues([':id' => 2, ':name' => 'user2'])->execute(); + $this->assertEquals('user2', $query->where(['id' => 2])->scalar($db)); + }, 10); + + + $db->enableQueryCache = true; + $query->cache(); + + $this->assertEquals('user11', $query->where(['id' => 1])->scalar($db)); + $update->bindValues([':id' => 1, ':name' => 'user1'])->execute(); + $this->assertEquals('user11', $query->where(['id' => 1])->scalar($db), 'When both Connection and Query have cache enabled, we get cached value'); + $this->assertEquals('user1', $query->noCache()->where(['id' => 1])->scalar($db), 'When Query has disabled cache, we get actual data'); + + $db->cache(function (Connection $db) use ($query, $update) { + $this->assertEquals('user1', $query->noCache()->where(['id' => 1])->scalar($db)); + $this->assertEquals('user11', $query->cache()->where(['id' => 1])->scalar($db)); + }, 10); + } }