Commit cc8ffe36 by Carsten Brandt

added support for yii\db\Expression to querybuilder BETWEEN and LIKE

fixes #6164
parent 1d7759e9
...@@ -31,6 +31,7 @@ Yii Framework 2 Change Log ...@@ -31,6 +31,7 @@ Yii Framework 2 Change Log
- Bug #6107: `yii message` was emptying existing translations in .po in case of multiple categories (samdark) - Bug #6107: `yii message` was emptying existing translations in .po in case of multiple categories (samdark)
- Bug #6112: `yii message` was incorrectly writing not yet translated strings in .po in case of multiple categories (samdark) - Bug #6112: `yii message` was incorrectly writing not yet translated strings in .po in case of multiple categories (samdark)
- Bug #6172: `yii\rbac\DbManager` should properly quote table and column names (qiangxue) - Bug #6172: `yii\rbac\DbManager` should properly quote table and column names (qiangxue)
- Bug #6164: Added missing support for `yii\db\Exression` QueryBuilder `BETWEEN` and `LIKE` conditions (cebe)
- Bug: Gii console command help information does not contain global options (qiangxue) - Bug: Gii console command help information does not contain global options (qiangxue)
- Bug: `yii\web\UrlRule` was unable to create URLs for rules containing unicode characters (samdark) - Bug: `yii\web\UrlRule` was unable to create URLs for rules containing unicode characters (samdark)
- Enh #4181: Added `yii\bootstrap\Modal::$headerOptions` and `yii\bootstrap\Modal::$footerOptions` (tuxoff, samdark) - Enh #4181: Added `yii\bootstrap\Modal::$headerOptions` and `yii\bootstrap\Modal::$footerOptions` (tuxoff, samdark)
......
...@@ -1020,10 +1020,24 @@ class QueryBuilder extends \yii\base\Object ...@@ -1020,10 +1020,24 @@ class QueryBuilder extends \yii\base\Object
if (strpos($column, '(') === false) { if (strpos($column, '(') === false) {
$column = $this->db->quoteColumnName($column); $column = $this->db->quoteColumnName($column);
} }
if ($value1 instanceof Expression) {
foreach ($value1->params as $n => $v) {
$params[$n] = $v;
}
$phName1 = $value1->expression;
} else {
$phName1 = self::PARAM_PREFIX . count($params); $phName1 = self::PARAM_PREFIX . count($params);
$params[$phName1] = $value1; $params[$phName1] = $value1;
}
if ($value2 instanceof Expression) {
foreach ($value2->params as $n => $v) {
$params[$n] = $v;
}
$phName2 = $value2->expression;
} else {
$phName2 = self::PARAM_PREFIX . count($params); $phName2 = self::PARAM_PREFIX . count($params);
$params[$phName2] = $value2; $params[$phName2] = $value2;
}
return "$column $operator $phName1 AND $phName2"; return "$column $operator $phName1 AND $phName2";
} }
...@@ -1181,7 +1195,9 @@ class QueryBuilder extends \yii\base\Object ...@@ -1181,7 +1195,9 @@ class QueryBuilder extends \yii\base\Object
list($column, $values) = $operands; list($column, $values) = $operands;
$values = (array) $values; if (!is_array($values)) {
$values = [$values];
}
if (empty($values)) { if (empty($values)) {
return $not ? '' : '0=1'; return $not ? '' : '0=1';
...@@ -1193,8 +1209,15 @@ class QueryBuilder extends \yii\base\Object ...@@ -1193,8 +1209,15 @@ class QueryBuilder extends \yii\base\Object
$parts = []; $parts = [];
foreach ($values as $value) { foreach ($values as $value) {
if ($value instanceof Expression) {
foreach ($value->params as $n => $v) {
$params[$n] = $v;
}
$phName = $value->expression;
} else {
$phName = self::PARAM_PREFIX . count($params); $phName = self::PARAM_PREFIX . count($params);
$params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%'); $params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%');
}
$parts[] = "$column $operator $phName"; $parts[] = "$column $operator $phName";
} }
......
...@@ -166,6 +166,16 @@ class QueryBuilderTest extends DatabaseTestCase ...@@ -166,6 +166,16 @@ class QueryBuilderTest extends DatabaseTestCase
[ ['or like', 'name', ['heyho', 'abc']], '`name` LIKE :qp0 OR `name` LIKE :qp1', [':qp0' => '%heyho%', ':qp1' => '%abc%'] ], [ ['or like', 'name', ['heyho', 'abc']], '`name` LIKE :qp0 OR `name` LIKE :qp1', [':qp0' => '%heyho%', ':qp1' => '%abc%'] ],
[ ['or not like', 'name', ['heyho', 'abc']], '`name` NOT LIKE :qp0 OR `name` NOT LIKE :qp1', [':qp0' => '%heyho%', ':qp1' => '%abc%'] ], [ ['or not like', 'name', ['heyho', 'abc']], '`name` NOT LIKE :qp0 OR `name` NOT LIKE :qp1', [':qp0' => '%heyho%', ':qp1' => '%abc%'] ],
// like with Expression
[ ['like', 'name', new Expression('CONCAT("test", colname, "%")')], '`name` LIKE CONCAT("test", colname, "%")', [] ],
[ ['not like', 'name', new Expression('CONCAT("test", colname, "%")')], '`name` NOT LIKE CONCAT("test", colname, "%")', [] ],
[ ['or like', 'name', new Expression('CONCAT("test", colname, "%")')], '`name` LIKE CONCAT("test", colname, "%")', [] ],
[ ['or not like', 'name', new Expression('CONCAT("test", colname, "%")')], '`name` NOT LIKE CONCAT("test", colname, "%")', [] ],
[ ['like', 'name', [new Expression('CONCAT("test", colname, "%")'), 'abc']], '`name` LIKE CONCAT("test", colname, "%") AND `name` LIKE :qp0', [':qp0' => '%abc%'] ],
[ ['not like', 'name', [new Expression('CONCAT("test", colname, "%")'), 'abc']], '`name` NOT LIKE CONCAT("test", colname, "%") AND `name` NOT LIKE :qp0', [':qp0' => '%abc%'] ],
[ ['or like', 'name', [new Expression('CONCAT("test", colname, "%")'), 'abc']], '`name` LIKE CONCAT("test", colname, "%") OR `name` LIKE :qp0', [':qp0' => '%abc%'] ],
[ ['or not like', 'name', [new Expression('CONCAT("test", colname, "%")'), 'abc']], '`name` NOT LIKE CONCAT("test", colname, "%") OR `name` NOT LIKE :qp0', [':qp0' => '%abc%'] ],
// not // not
[ ['not', 'name'], 'NOT (name)', [] ], [ ['not', 'name'], 'NOT (name)', [] ],
...@@ -177,10 +187,13 @@ class QueryBuilderTest extends DatabaseTestCase ...@@ -177,10 +187,13 @@ class QueryBuilderTest extends DatabaseTestCase
[ ['or', 'id=1', 'id=2'], '(id=1) OR (id=2)', [] ], [ ['or', 'id=1', 'id=2'], '(id=1) OR (id=2)', [] ],
[ ['or', 'type=1', ['or', 'id=1', 'id=2']], '(type=1) OR ((id=1) OR (id=2))', [] ], [ ['or', 'type=1', ['or', 'id=1', 'id=2']], '(type=1) OR ((id=1) OR (id=2))', [] ],
// between // between
[ ['between', 'id', 1, 10], '`id` BETWEEN :qp0 AND :qp1', [':qp0' => 1, ':qp1' => 10] ], [ ['between', 'id', 1, 10], '`id` BETWEEN :qp0 AND :qp1', [':qp0' => 1, ':qp1' => 10] ],
[ ['not between', 'id', 1, 10], '`id` NOT BETWEEN :qp0 AND :qp1', [':qp0' => 1, ':qp1' => 10] ], [ ['not between', 'id', 1, 10], '`id` NOT BETWEEN :qp0 AND :qp1', [':qp0' => 1, ':qp1' => 10] ],
[ ['between', 'date', new Expression('(NOW() - INTERVAL 1 MONTH)'), new Expression('NOW()')], '`date` BETWEEN (NOW() - INTERVAL 1 MONTH) AND NOW()', [] ],
[ ['between', 'date', new Expression('(NOW() - INTERVAL 1 MONTH)'), 123], '`date` BETWEEN (NOW() - INTERVAL 1 MONTH) AND :qp0', [':qp0' => 123] ],
[ ['not between', 'date', new Expression('(NOW() - INTERVAL 1 MONTH)'), new Expression('NOW()')], '`date` NOT BETWEEN (NOW() - INTERVAL 1 MONTH) AND NOW()', [] ],
[ ['not between', 'date', new Expression('(NOW() - INTERVAL 1 MONTH)'), 123], '`date` NOT BETWEEN (NOW() - INTERVAL 1 MONTH) AND :qp0', [':qp0' => 123] ],
// in // in
[ ['in', 'id', [1, 2, 3]], '`id` IN (:qp0, :qp1, :qp2)', [':qp0' => 1, ':qp1' => 2, ':qp2' => 3] ], [ ['in', 'id', [1, 2, 3]], '`id` IN (:qp0, :qp1, :qp2)', [':qp0' => 1, ':qp1' => 2, ':qp2' => 3] ],
...@@ -194,7 +207,6 @@ class QueryBuilderTest extends DatabaseTestCase ...@@ -194,7 +207,6 @@ class QueryBuilderTest extends DatabaseTestCase
[ ['in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], '(`id`, `name`) IN (SELECT `id`, `name` FROM `users` WHERE `active`=:qp0)', [':qp0' => 1] ], [ ['in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], '(`id`, `name`) IN (SELECT `id`, `name` FROM `users` WHERE `active`=:qp0)', [':qp0' => 1] ],
[ ['not in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], '(`id`, `name`) NOT IN (SELECT `id`, `name` FROM `users` WHERE `active`=:qp0)', [':qp0' => 1] ], [ ['not in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], '(`id`, `name`) NOT IN (SELECT `id`, `name` FROM `users` WHERE `active`=:qp0)', [':qp0' => 1] ],
// exists // exists
[ ['exists', (new Query())->select('id')->from('users')->where(['active' => 1])], 'EXISTS (SELECT `id` FROM `users` WHERE `active`=:qp0)', [':qp0' => 1] ], [ ['exists', (new Query())->select('id')->from('users')->where(['active' => 1])], 'EXISTS (SELECT `id` FROM `users` WHERE `active`=:qp0)', [':qp0' => 1] ],
[ ['not exists', (new Query())->select('id')->from('users')->where(['active' => 1])], 'NOT EXISTS (SELECT `id` FROM `users` WHERE `active`=:qp0)', [':qp0' => 1] ], [ ['not exists', (new Query())->select('id')->from('users')->where(['active' => 1])], 'NOT EXISTS (SELECT `id` FROM `users` WHERE `active`=:qp0)', [':qp0' => 1] ],
...@@ -209,6 +221,11 @@ class QueryBuilderTest extends DatabaseTestCase ...@@ -209,6 +221,11 @@ class QueryBuilderTest extends DatabaseTestCase
[ ['!=', 'a', 'b'], '`a` != :qp0', [':qp0' => 'b'] ], [ ['!=', 'a', 'b'], '`a` != :qp0', [':qp0' => 'b'] ],
[ ['>=', 'date', new Expression('DATE_SUB(NOW(), INTERVAL 1 MONTH)')], '`date` >= DATE_SUB(NOW(), INTERVAL 1 MONTH)', [] ], [ ['>=', 'date', new Expression('DATE_SUB(NOW(), INTERVAL 1 MONTH)')], '`date` >= DATE_SUB(NOW(), INTERVAL 1 MONTH)', [] ],
[ ['>=', 'date', new Expression('DATE_SUB(NOW(), INTERVAL :month MONTH)', [':month' => 2])], '`date` >= DATE_SUB(NOW(), INTERVAL :month MONTH)', [':month' => 2] ], [ ['>=', 'date', new Expression('DATE_SUB(NOW(), INTERVAL :month MONTH)', [':month' => 2])], '`date` >= DATE_SUB(NOW(), INTERVAL :month MONTH)', [':month' => 2] ],
// hash condition
[ ['a' => 1, 'b' => 2], '(`a`=:qp0) AND (`b`=:qp1)', [':qp0' => 1, ':qp1' => 2] ],
[ ['a' => new Expression('CONCAT(col1, col2)'), 'b' => 2], '(`a`=CONCAT(col1, col2)) AND (`b`=:qp0)', [':qp0' => 2] ],
]; ];
// adjust dbms specific escaping // adjust dbms specific escaping
......
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