Commit b6561375 by Alexander Makarov

Added support for arbitrary operators to Query::filterWhere, added support for…

Added support for arbitrary operators to Query::filterWhere, added support for arbitrary operators to Sphinx extension
parent d37d9228
...@@ -259,7 +259,7 @@ $query->where(['>=', 'id', 10]); ...@@ -259,7 +259,7 @@ $query->where(['>=', 'id', 10]);
It will result in: It will result in:
```sql ```sql
SELECT id FROM user WHERE id > 10; SELECT id FROM user WHERE id >= 10;
``` ```
If you are building parts of condition dynamically it's very convenient to use `andWhere()` and `orWhere()`: If you are building parts of condition dynamically it's very convenient to use `andWhere()` and `orWhere()`:
......
...@@ -617,12 +617,11 @@ class QueryBuilder extends Object ...@@ -617,12 +617,11 @@ class QueryBuilder extends Object
$operator = strtoupper($condition[0]); $operator = strtoupper($condition[0]);
if (isset($builders[$operator])) { if (isset($builders[$operator])) {
$method = $builders[$operator]; $method = $builders[$operator];
array_shift($condition);
return $this->$method($indexes, $operator, $condition, $params);
} else { } else {
throw new Exception('Found unknown operator in query: ' . $operator); $method = 'buildSimpleCondition';
} }
array_shift($condition);
return $this->$method($indexes, $operator, $condition, $params);
} else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ... } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
return $this->buildHashCondition($indexes, $condition, $params); return $this->buildHashCondition($indexes, $condition, $params);
...@@ -986,4 +985,29 @@ class QueryBuilder extends Object ...@@ -986,4 +985,29 @@ class QueryBuilder extends Object
return $phName; return $phName;
} }
} }
/**
* Creates an SQL expressions like `"column" operator value`.
* @param string $operator the operator to use. Anything could be used e.g. `>`, `<=`, etc.
* @param array $operands contains two column names.
* @param array $params the binding parameters to be populated
* @return string the generated SQL expression
*/
public function buildSimpleCondition($operator, $operands, &$params)
{
if (empty($operands[0]) || !array_key_exists(1, $operands)) {
throw new InvalidParamException("Operator '$operator' requires two operands.");
}
list($column, $value) = $operands;
if (strpos($column, '(') === false) {
$column = $this->db->quoteColumnName($column);
}
$phName = self::PARAM_PREFIX . count($params);
$params[$phName] = $value === null ? 'NULL' : $value;
return "$column $operator $phName";
}
} }
...@@ -1203,7 +1203,7 @@ class QueryBuilder extends \yii\base\Object ...@@ -1203,7 +1203,7 @@ class QueryBuilder extends \yii\base\Object
*/ */
public function buildSimpleCondition($operator, $operands, &$params) public function buildSimpleCondition($operator, $operands, &$params)
{ {
if (!isset($operands[0], $operands[1])) { if (empty($operands[0]) || !array_key_exists(1, $operands)) {
throw new InvalidParamException("Operator '$operator' requires two operands."); throw new InvalidParamException("Operator '$operator' requires two operands.");
} }
...@@ -1213,7 +1213,7 @@ class QueryBuilder extends \yii\base\Object ...@@ -1213,7 +1213,7 @@ class QueryBuilder extends \yii\base\Object
$column = $this->db->quoteColumnName($column); $column = $this->db->quoteColumnName($column);
} }
$phName = self::PARAM_PREFIX . 0; $phName = self::PARAM_PREFIX . count($params);
$params[$phName] = $value === null ? 'NULL' : $value; $params[$phName] = $value === null ? 'NULL' : $value;
return "$column $operator $phName"; return "$column $operator $phName";
......
...@@ -253,6 +253,14 @@ trait QueryTrait ...@@ -253,6 +253,14 @@ trait QueryTrait
return []; return [];
} }
break; break;
case 'BETWEEN':
case 'NOT BETWEEN':
if (array_key_exists(1, $condition) && array_key_exists(2, $condition)) {
if ($this->isEmpty($condition[1]) || $this->isEmpty($condition[2])) {
return [];
}
}
break;
case 'IN': case 'IN':
case 'NOT IN': case 'NOT IN':
case 'LIKE': case 'LIKE':
...@@ -263,20 +271,10 @@ trait QueryTrait ...@@ -263,20 +271,10 @@ trait QueryTrait
case 'OR ILIKE': case 'OR ILIKE':
case 'NOT ILIKE': case 'NOT ILIKE':
case 'OR NOT ILIKE': case 'OR NOT ILIKE':
default:
if (array_key_exists(1, $condition) && $this->isEmpty($condition[1])) { if (array_key_exists(1, $condition) && $this->isEmpty($condition[1])) {
return []; return [];
} }
break;
case 'BETWEEN':
case 'NOT BETWEEN':
if (array_key_exists(1, $condition) && array_key_exists(2, $condition)) {
if ($this->isEmpty($condition[1]) || $this->isEmpty($condition[2])) {
return [];
}
}
break;
default:
throw new NotSupportedException("Operator not supported: $operator");
} }
array_unshift($condition, $operator); array_unshift($condition, $operator);
......
...@@ -198,6 +198,63 @@ class QueryBuilderTest extends DatabaseTestCase ...@@ -198,6 +198,63 @@ class QueryBuilderTest extends DatabaseTestCase
return $conditions; return $conditions;
} }
public function filterConditionProvider()
{
$conditions = [
// like
[ ['like', 'name', []], '', [] ],
[ ['not like', 'name', []], '', [] ],
[ ['or like', 'name', []], '', [] ],
[ ['or not like', 'name', []], '', [] ],
// not
[ ['not', ''], '', [] ],
// and
[ ['and', '', ''], '', [] ],
[ ['and', '', 'id=2'], '(id=2)', [] ],
[ ['and', 'id=1', ''], '(id=1)', [] ],
[ ['and', 'type=1', ['or', '', 'id=2']], '(type=1) AND ((id=2))', [] ],
// or
[ ['or', 'id=1', ''], '(id=1)', [] ],
[ ['or', 'type=1', ['or', '', 'id=2']], '(type=1) OR ((id=2))', [] ],
// between
[ ['between', 'id', 1, null], '', [] ],
[ ['not between', 'id', null, 10], '', [] ],
// in
[ ['in', 'id', []], '', [] ],
[ ['not in', 'id', []], '', [] ],
// TODO: exists and not exists
// simple conditions
[ ['=', 'a', ''], '', [] ],
[ ['>', 'a', ''], '', [] ],
[ ['>=', 'a', ''], '', [] ],
[ ['<', 'a', ''], '', [] ],
[ ['<=', 'a', ''], '', [] ],
[ ['<>', 'a', ''], '', [] ],
[ ['!=', 'a', ''], '', [] ],
];
// adjust dbms specific escaping
foreach($conditions as $i => $condition) {
switch ($this->driverName) {
case 'mssql':
case 'mysql':
case 'sqlite':
$conditions[$i][1] = str_replace('"', '`', $condition[1]);
break;
}
}
return $conditions;
}
/** /**
* @dataProvider conditionProvider * @dataProvider conditionProvider
*/ */
...@@ -209,6 +266,17 @@ class QueryBuilderTest extends DatabaseTestCase ...@@ -209,6 +266,17 @@ class QueryBuilderTest extends DatabaseTestCase
$this->assertEquals('SELECT *' . (empty($expected) ? '' : ' WHERE ' . $expected), $sql); $this->assertEquals('SELECT *' . (empty($expected) ? '' : ' WHERE ' . $expected), $sql);
} }
/**
* @dataProvider filterConditionProvider
*/
public function testBuildFilterCondition($condition, $expected, $expectedParams)
{
$query = (new Query())->filterWhere($condition);
list($sql, $params) = $this->getQueryBuilder()->build($query);
$this->assertEquals($expectedParams, $params);
$this->assertEquals('SELECT *' . (empty($expected) ? '' : ' WHERE ' . $expected), $sql);
}
public function testAddDropPrimaryKey() public function testAddDropPrimaryKey()
{ {
$tableName = 'constraints'; $tableName = 'constraints';
......
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