Commit 7d08a096 by Qiang Xue

Fixes #1809: Added support for building "EXISTS" and "NOT EXISTS" query conditions

parent fa854477
...@@ -55,6 +55,7 @@ Yii Framework 2 Change Log ...@@ -55,6 +55,7 @@ Yii Framework 2 Change Log
- Enh #1681: Added support for automatically adjusting the "for" attribute of label generated by `ActiveField::label()` (qiangxue) - Enh #1681: Added support for automatically adjusting the "for" attribute of label generated by `ActiveField::label()` (qiangxue)
- Enh #1706: Added support for registering a single JS/CSS file with dependency (qiangxue) - Enh #1706: Added support for registering a single JS/CSS file with dependency (qiangxue)
- Enh #1773: keyPrefix property of Cache is not restricted to alnum characters anymore, however it is still recommended (cebe) - Enh #1773: keyPrefix property of Cache is not restricted to alnum characters anymore, however it is still recommended (cebe)
- Enh #1809: Added support for building "EXISTS" and "NOT EXISTS" query conditions (abdrasulov)
- Enh #1852: ActiveRecord::tableName() now returns table name using DbConnection::tablePrefix (creocoder) - Enh #1852: ActiveRecord::tableName() now returns table name using DbConnection::tablePrefix (creocoder)
- Enh: Added `favicon.ico` and `robots.txt` to default application templates (samdark) - Enh: Added `favicon.ico` and `robots.txt` to default application templates (samdark)
- Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue) - Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue)
......
...@@ -799,6 +799,8 @@ class QueryBuilder extends \yii\base\Object ...@@ -799,6 +799,8 @@ class QueryBuilder extends \yii\base\Object
'NOT LIKE' => 'buildLikeCondition', 'NOT LIKE' => 'buildLikeCondition',
'OR LIKE' => 'buildLikeCondition', 'OR LIKE' => 'buildLikeCondition',
'OR NOT LIKE' => 'buildLikeCondition', 'OR NOT LIKE' => 'buildLikeCondition',
'EXISTS' => 'buildExistsCondition',
'NOT EXISTS' => 'buildExistsCondition',
]; ];
if (!is_array($condition)) { if (!is_array($condition)) {
...@@ -1071,4 +1073,21 @@ class QueryBuilder extends \yii\base\Object ...@@ -1071,4 +1073,21 @@ class QueryBuilder extends \yii\base\Object
return implode($andor, $parts); return implode($andor, $parts);
} }
/**
* Creates an SQL expressions with the `EXISTS` operator.
* @param string $operator the operator to use (e.g. `EXISTS` or `NOT EXISTS`)
* @param array $operands contains only the one element. It is sub-query
* @param array $params the binding parameters to be populated
* @return string the generated SQL expression
*/
public function buildExistsCondition($operator, $operands, &$params)
{
$subQuery = $operands[0];
list($subQuerySql, $subQueryParams) = $this->build($subQuery);
foreach ($subQueryParams as $name => $value) {
$params[$name] = $value;
}
return "$operator ($subQuerySql)";
}
} }
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace yiiunit\framework\db; namespace yiiunit\framework\db;
use yii\db\Query;
use yii\db\QueryBuilder; use yii\db\QueryBuilder;
use yii\db\Schema; use yii\db\Schema;
use yii\db\mysql\QueryBuilder as MysqlQueryBuilder; use yii\db\mysql\QueryBuilder as MysqlQueryBuilder;
...@@ -130,4 +131,86 @@ class QueryBuilderTest extends DatabaseTestCase ...@@ -130,4 +131,86 @@ class QueryBuilderTest extends DatabaseTestCase
$tableSchema = $qb->db->getSchema()->getTableSchema($tableName); $tableSchema = $qb->db->getSchema()->getTableSchema($tableName);
$this->assertEquals(0, count($tableSchema->primaryKey)); $this->assertEquals(0, count($tableSchema->primaryKey));
} }
public function testBuildWhereExists()
{
$expectedQuerySql = "SELECT `id` FROM `TotalExample` `t` WHERE EXISTS (SELECT `1` FROM `Website` `w`)";
$expectedQueryParams = null;
$subQuery = new Query();
$subQuery->select('1')
->from('Website w');
$query = new Query();
$query->select('id')
->from('TotalExample t')
->where(['exists', $subQuery]);
list($actualQuerySql, $actualQueryParams) = $this->getQueryBuilder()->build($query);
$this->assertEquals($expectedQuerySql, $actualQuerySql);
$this->assertEquals($expectedQueryParams, $actualQueryParams);
}
public function testBuildWhereNotExists()
{
$expectedQuerySql = "SELECT `id` FROM `TotalExample` `t` WHERE NOT EXISTS (SELECT `1` FROM `Website` `w`)";
$expectedQueryParams = null;
$subQuery = new Query();
$subQuery->select('1')
->from('Website w');
$query = new Query();
$query->select('id')
->from('TotalExample t')
->where(['not exists', $subQuery]);
list($actualQuerySql, $actualQueryParams) = $this->getQueryBuilder()->build($query);
$this->assertEquals($expectedQuerySql, $actualQuerySql);
$this->assertEquals($expectedQueryParams, $actualQueryParams);
}
public function testBuildWhereExistsWithParameters()
{
$expectedQuerySql = "SELECT `id` FROM `TotalExample` `t` WHERE (EXISTS (SELECT `1` FROM `Website` `w` WHERE (w.id = t.website_id) AND (w.merchant_id = :merchant_id))) AND (t.some_column = :some_value)";
$expectedQueryParams = [':some_value' => "asd", ':merchant_id' => 6];
$subQuery = new Query();
$subQuery->select('1')
->from('Website w')
->where('w.id = t.website_id')
->andWhere('w.merchant_id = :merchant_id', [':merchant_id' => 6]);
$query = new Query();
$query->select('id')
->from('TotalExample t')
->where(['exists', $subQuery])
->andWhere('t.some_column = :some_value', [':some_value' => "asd"]);
list($actualQuerySql, $queryParams) = $this->getQueryBuilder()->build($query);
$this->assertEquals($expectedQuerySql, $actualQuerySql);
$this->assertEquals($expectedQueryParams, $queryParams);
}
public function testBuildWhereExistsWithArrayParameters()
{
$expectedQuerySql = "SELECT `id` FROM `TotalExample` `t` WHERE (EXISTS (SELECT `1` FROM `Website` `w` WHERE (w.id = t.website_id) AND ((`w`.`merchant_id`=:qp0) AND (`w`.`user_id`=:qp1)))) AND (`t`.`some_column`=:qp2)";
$expectedQueryParams = [':qp0' => 6, ':qp1' => 210, ':qp2' => 'asd'];
$subQuery = new Query();
$subQuery->select('1')
->from('Website w')
->where('w.id = t.website_id')
->andWhere(['w.merchant_id' => 6, 'w.user_id' => '210']);
$query = new Query();
$query->select('id')
->from('TotalExample t')
->where(['exists', $subQuery])
->andWhere(['t.some_column' => "asd"]);
list($actualQuerySql, $queryParams) = $this->getQueryBuilder()->build($query);
$this->assertEquals($expectedQuerySql, $actualQuerySql);
$this->assertEquals($expectedQueryParams, $queryParams);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment