Browse Source

Added `QueryInterface::emulateExecution()`

Added `QueryInterface::emulateExecution()`, which allows preventing of
the actual query execution.
This allows to cancel `DataProvider` preventing search query execution
in case search model is invalid:

``` php
public function search($params)
    {
        $query = Item::find();

        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);

        $this->load($params);

        if (!$this->validate()) {
            $query->where('0=1');
            $query->emulateExecution(); // No SQL execution will be done
            return $dataProvider;
        }
```

This also fix unecessary query in case of `via()` usage. See #12390.

fixes #12390
fixes #6373
close #12708
tags/2.0.11
Klimov Paul 8 years ago committed by Carsten Brandt
parent
commit
c17766181f
  1. 6
      framework/CHANGELOG.md
  2. 6
      framework/UPGRADE.md
  3. 6
      framework/db/ActiveRelationTrait.php
  4. 29
      framework/db/Query.php
  5. 12
      framework/db/QueryInterface.php
  6. 22
      framework/db/QueryTrait.php
  7. 66
      tests/framework/db/ActiveRecordTest.php
  8. 70
      tests/framework/db/QueryTest.php

6
framework/CHANGELOG.md

@ -28,7 +28,8 @@ Yii Framework 2 Change Log
- Bug #13089: Fixed `yii\console\controllers\AssetController::adjustCssUrl()` breaks URL reference specification (`url(#id)`) (vitalyzhakov)
- Bug #7727: Fixed truncateHtml leaving extra tags (developeruz)
- Bug #13118: Fixed `handleAction()` function in `yii.js` to handle attribute `data-pjax=0` as disabled PJAX (silverfire)
- Enh #6809: Added `\yii\caching\Cache::$defaultDuration` property, allowing to set custom default cache duration (sdkiller)
- Enh #6373: Introduce `yii\db\Query::emulateExecution()` to force returning an empty result for a query (klimov-paul)
- Enh #6809: Added `yii\caching\Cache::$defaultDuration` property, allowing to set custom default cache duration (sdkiller)
- Enh #7333: Improved error message for `yii\di\Instance::ensure()` when a component does not exist (cebe)
- Enh #7420: Attributes for prompt generated with `renderSelectOptions` of `\yii\helpers\Html` helper (arogachev)
- Enh #9162: Added support of closures in `value` for attributes in `yii\widgets\DetailView` (arogachev)
@ -37,6 +38,7 @@ Yii Framework 2 Change Log
- Enh #11756: Added type mapping for `varbinary` data type in MySQL DBMS (silverfire)
- Enh #11929: Changed `type` column type from `int` to `smallInt` in RBAC migrations (silverfire)
- Enh #12015: Changed visibility `yii\db\ActiveQueryTrait::createModels()` from private to protected (ArekX, dynasource)
- Enh #12390: Avoid creating queries with false where contdition (`0=1`) when fetching relational data (klimov-paul)
- Enh #12619: Added catch `Throwable` in `yii\base\ErrorHandler::handleException()` (rob006)
- Enh #12726: `yii\base\Application::$version` converted to `yii\base\Module::$version` virtual property, allowing to specify version as a PHP callback (klimov-paul)
- Enh #12738: Added support for creating protocol-relative URLs in `UrlManager::createAbsoluteUrl()` and `Url` helper methods (rob006)
@ -53,7 +55,7 @@ Yii Framework 2 Change Log
- Enh #13036: Added shortcut methods `asJson()` and `asXml()` for returning JSON and XML data in web controller actions (cebe)
- Enh #13020: Added `disabledListItemSubTagOptions` attribute for `yii\widgets\LinkPager` in order to customize the disabled list item sub tag element (nadar)
- Enh #12988: Changed `textarea` method within the `yii\helpers\BaseHtml` class to allow users to control whether html entities found within `$value` will be double-encoded or not (cyphix333)
- Enh #13074: Improved `\yii\log\SyslogTarget` with `$options` to be able to change the default `openlog` options. (timbeks)
- Enh #13074: Improved `yii\log\SyslogTarget` with `$options` to be able to change the default `openlog` options. (timbeks)
- Enh #13050: Added `yii\filters\HostControl` allowing protection against 'host header' attacks (klimov-paul)
- Enh: Added constants for specifying `yii\validators\CompareValidator::$type` (cebe)
- Enh #12854: Added `RangeNotSatisfiableHttpException` to cover HTTP error 416 file request exceptions (zalatov)

6
framework/UPGRADE.md

@ -50,6 +50,12 @@ if you want to upgrade from version A to version C and there is
version B between A and C, you need to follow the instructions
for both A and B.
Upgrade from Yii 2.0.10
-----------------------
* A new method `public function emulateExecution($value = true);` has been added to the `yii\db\QueryInterace`.
This method is implemented in the `yii\db\QueryTrait`, so this only affects your code if you implement QueryInterface
in a class that does not use the trait.
Upgrade from Yii 2.0.9
----------------------

6
framework/db/ActiveRelationTrait.php

@ -464,6 +464,9 @@ trait ActiveRelationTrait
}
}
}
if (empty($values)) {
$this->emulateExecution();
}
} else {
// composite keys
@ -478,6 +481,9 @@ trait ActiveRelationTrait
$v[$attribute] = $model[$link];
}
$values[] = $v;
if (empty($v)) {
$this->emulateExecution();
}
}
}
$this->andWhere(['in', $attributes, array_unique($values, SORT_REGULAR)]);

29
framework/db/Query.php

@ -207,6 +207,9 @@ class Query extends Component implements QueryInterface
*/
public function all($db = null)
{
if ($this->emulateExecution) {
return [];
}
$rows = $this->createCommand($db)->queryAll();
return $this->populate($rows);
}
@ -244,6 +247,9 @@ class Query extends Component implements QueryInterface
*/
public function one($db = null)
{
if ($this->emulateExecution) {
return false;
}
return $this->createCommand($db)->queryOne();
}
@ -257,6 +263,9 @@ class Query extends Component implements QueryInterface
*/
public function scalar($db = null)
{
if ($this->emulateExecution) {
return null;
}
return $this->createCommand($db)->queryScalar();
}
@ -268,6 +277,10 @@ class Query extends Component implements QueryInterface
*/
public function column($db = null)
{
if ($this->emulateExecution) {
return [];
}
if ($this->indexBy === null) {
return $this->createCommand($db)->queryColumn();
}
@ -300,6 +313,9 @@ class Query extends Component implements QueryInterface
*/
public function count($q = '*', $db = null)
{
if ($this->emulateExecution) {
return 0;
}
return $this->queryScalar("COUNT($q)", $db);
}
@ -313,6 +329,9 @@ class Query extends Component implements QueryInterface
*/
public function sum($q, $db = null)
{
if ($this->emulateExecution) {
return 0;
}
return $this->queryScalar("SUM($q)", $db);
}
@ -326,6 +345,9 @@ class Query extends Component implements QueryInterface
*/
public function average($q, $db = null)
{
if ($this->emulateExecution) {
return 0;
}
return $this->queryScalar("AVG($q)", $db);
}
@ -363,6 +385,9 @@ class Query extends Component implements QueryInterface
*/
public function exists($db = null)
{
if ($this->emulateExecution) {
return false;
}
$command = $this->createCommand($db);
$params = $command->params;
$command->setSql($command->db->getQueryBuilder()->selectExists($command->getSql()));
@ -379,6 +404,10 @@ class Query extends Component implements QueryInterface
*/
protected function queryScalar($selectExpression, $db)
{
if ($this->emulateExecution) {
return null;
}
$select = $this->select;
$limit = $this->limit;
$offset = $this->offset;

12
framework/db/QueryInterface.php

@ -252,4 +252,16 @@ interface QueryInterface
* @return $this the query object itself
*/
public function offset($offset);
/**
* Sets whether to emulate query execution, preventing any interaction with data storage.
* After this mode is enabled, methods, returning query results like [[one()]], [[all()]], [[exists()]]
* and so on, will return empty or false values.
* You should use this method in case your program logic indicates query should not return any results, like
* in case you set false where condition like `0=1`.
* @param boolean $value whether to prevent query execution.
* @return $this the query object itself.
* @since 2.0.11
*/
public function emulateExecution($value = true);
}

22
framework/db/QueryTrait.php

@ -50,6 +50,12 @@ trait QueryTrait
* row data. For more details, see [[indexBy()]]. This property is only used by [[QueryInterface::all()|all()]].
*/
public $indexBy;
/**
* @var boolean whether to emulate the actual query execution, returning empty or false results.
* @see emulateExecution()
* @since 2.0.11
*/
public $emulateExecution = false;
/**
@ -388,4 +394,20 @@ trait QueryTrait
$this->offset = $offset;
return $this;
}
/**
* Sets whether to emulate query execution, preventing any interaction with data storage.
* After this mode is enabled, methods, returning query results like [[one()]], [[all()]], [[exists()]]
* and so on, will return empty or false values.
* You should use this method in case your program logic indicates query should not return any results, like
* in case you set false where condition like `0=1`.
* @param boolean $value whether to prevent query execution.
* @return $this the query object itself.
* @since 2.0.11
*/
public function emulateExecution($value = true)
{
$this->emulateExecution = $value;
return $this;
}
}

66
tests/framework/db/ActiveRecordTest.php

@ -1294,4 +1294,70 @@ abstract class ActiveRecordTest extends DatabaseTestCase
$this->assertEquals($newTotal, $newOrder->total);
}
public function testEmulateExecution()
{
$this->assertGreaterThan(0, Customer::find()->from('customer')->count());
$rows = Customer::find()
->from('customer')
->emulateExecution()
->all();
$this->assertSame([], $rows);
$row = Customer::find()
->from('customer')
->emulateExecution()
->one();
$this->assertSame(null, $row);
$exists = Customer::find()
->from('customer')
->emulateExecution()
->exists();
$this->assertSame(false, $exists);
$count = Customer::find()
->from('customer')
->emulateExecution()
->count();
$this->assertSame(0, $count);
$sum = Customer::find()
->from('customer')
->emulateExecution()
->sum('id');
$this->assertSame(0, $sum);
$sum = Customer::find()
->from('customer')
->emulateExecution()
->average('id');
$this->assertSame(0, $sum);
$max = Customer::find()
->from('customer')
->emulateExecution()
->max('id');
$this->assertSame(null, $max);
$min = Customer::find()
->from('customer')
->emulateExecution()
->min('id');
$this->assertSame(null, $min);
$scalar = Customer::find()
->select(['id'])
->from('customer')
->emulateExecution()
->scalar();
$this->assertSame(null, $scalar);
$column = Customer::find()
->select(['id'])
->from('customer')
->emulateExecution()
->column();
$this->assertSame([], $column);
}
}

70
tests/framework/db/QueryTest.php

@ -2,6 +2,7 @@
namespace yiiunit\framework\db;
use yii\db\Connection;
use yii\db\Expression;
use yii\db\Query;
@ -317,4 +318,73 @@ abstract class QueryTest extends DatabaseTestCase
$count = (new Query)->from('customer')->having(['status' => 2])->count('*', $db);
$this->assertEquals(1, $count);
}
public function testEmulateExecution()
{
$db = $this->getConnection();
$this->assertGreaterThan(0, (new Query())->from('customer')->count('*', $db));
$rows = (new Query())
->from('customer')
->emulateExecution()
->all($db);
$this->assertSame([], $rows);
$row = (new Query())
->from('customer')
->emulateExecution()
->one($db);
$this->assertSame(false, $row);
$exists = (new Query())
->from('customer')
->emulateExecution()
->exists($db);
$this->assertSame(false, $exists);
$count = (new Query())
->from('customer')
->emulateExecution()
->count('*', $db);
$this->assertSame(0, $count);
$sum = (new Query())
->from('customer')
->emulateExecution()
->sum('id', $db);
$this->assertSame(0, $sum);
$sum = (new Query())
->from('customer')
->emulateExecution()
->average('id', $db);
$this->assertSame(0, $sum);
$max = (new Query())
->from('customer')
->emulateExecution()
->max('id', $db);
$this->assertSame(null, $max);
$min = (new Query())
->from('customer')
->emulateExecution()
->min('id', $db);
$this->assertSame(null, $min);
$scalar = (new Query())
->select(['id'])
->from('customer')
->emulateExecution()
->scalar($db);
$this->assertSame(null, $scalar);
$column = (new Query())
->select(['id'])
->from('customer')
->emulateExecution()
->column($db);
$this->assertSame([], $column);
}
}

Loading…
Cancel
Save