Commit e62e8487 by Carsten Brandt

more API methods for redis active query: sum, avg, max, min ...

parent 7817815d
...@@ -56,11 +56,6 @@ class ActiveQuery extends \yii\base\Component ...@@ -56,11 +56,6 @@ class ActiveQuery extends \yii\base\Component
*/ */
public $with; public $with;
/** /**
* @var string the name of the column by which query results should be indexed by.
* This is only used when the query result is returned as an array when calling [[all()]].
*/
public $indexBy;
/**
* @var boolean whether to return each record as an array. If false (default), an object * @var boolean whether to return each record as an array. If false (default), an object
* of [[modelClass]] will be created to represent each record. * of [[modelClass]] will be created to represent each record.
*/ */
...@@ -80,6 +75,18 @@ class ActiveQuery extends \yii\base\Component ...@@ -80,6 +75,18 @@ class ActiveQuery extends \yii\base\Component
* If less than zero it means starting n elements from the end. * If less than zero it means starting n elements from the end.
*/ */
public $offset; public $offset;
/**
* @var array how to sort the query results. This is used to construct the ORDER BY clause in a SQL statement.
* The array keys are the columns to be sorted by, and the array values are the corresponding sort directions which
* can be either [[Query::SORT_ASC]] or [[Query::SORT_DESC]]. The array may also contain [[Expression]] objects.
* If that is the case, the expressions will be converted into strings without any change.
*/
public $orderBy;
/**
* @var string the name of the column by which query results should be indexed by.
* This is only used when the query result is returned as an array when calling [[all()]].
*/
public $indexBy;
/** /**
* Executes query and returns all results as an array. * Executes query and returns all results as an array.
...@@ -154,6 +161,21 @@ class ActiveQuery extends \yii\base\Component ...@@ -154,6 +161,21 @@ class ActiveQuery extends \yii\base\Component
} }
/** /**
* Executes the query and returns the first column of the result.
* @param string $column name of the column to select
* @return array the first column of the query result. An empty array is returned if the query results in nothing.
*/
public function column($column)
{
// TODO add support for indexBy and orderBy
$modelClass = $this->modelClass;
/** @var Connection $db */
$db = $modelClass::getDb();
$script = $db->luaScriptBuilder->buildColumn($this, $column);
return $db->executeCommand('EVAL', array($script, 0));
}
/**
* Returns the number of records. * Returns the number of records.
* @param string $q the COUNT expression. Defaults to '*'. * @param string $q the COUNT expression. Defaults to '*'.
* Make sure you properly quote column names. * Make sure you properly quote column names.
...@@ -187,8 +209,54 @@ class ActiveQuery extends \yii\base\Component ...@@ -187,8 +209,54 @@ class ActiveQuery extends \yii\base\Component
} }
/** /**
* Returns the average of the specified column values.
* @param string $column the column name or expression.
* Make sure you properly quote column names in the expression.
* @return integer the average of the specified column values.
*/
public function average($column)
{
$modelClass = $this->modelClass;
/** @var Connection $db */
$db = $modelClass::getDb();
$script = $db->luaScriptBuilder->buildAverage($this, $column);
return $db->executeCommand('EVAL', array($script, 0));
}
/**
* Returns the minimum of the specified column values.
* @param string $column the column name or expression.
* Make sure you properly quote column names in the expression.
* @return integer the minimum of the specified column values.
*/
public function min($column)
{
$modelClass = $this->modelClass;
/** @var Connection $db */
$db = $modelClass::getDb();
$script = $db->luaScriptBuilder->buildMin($this, $column);
return $db->executeCommand('EVAL', array($script, 0));
}
/**
* Returns the maximum of the specified column values.
* @param string $column the column name or expression.
* Make sure you properly quote column names in the expression.
* @return integer the maximum of the specified column values.
*/
public function max($column)
{
$modelClass = $this->modelClass;
/** @var Connection $db */
$db = $modelClass::getDb();
$script = $db->luaScriptBuilder->buildMax($this, $column);
return $db->executeCommand('EVAL', array($script, 0));
}
/**
* Returns the query result as a scalar value. * Returns the query result as a scalar value.
* The value returned will be the first column in the first row of the query results. * The value returned will be the first column in the first row of the query results.
* @param string $column name of the column to select
* @return string|boolean the value of the first column in the first row of the query result. * @return string|boolean the value of the first column in the first row of the query result.
* False is returned if the query result is empty. * False is returned if the query result is empty.
*/ */
...@@ -210,7 +278,6 @@ class ActiveQuery extends \yii\base\Component ...@@ -210,7 +278,6 @@ class ActiveQuery extends \yii\base\Component
/** /**
* Sets the [[asArray]] property. * Sets the [[asArray]] property.
* TODO: refactor, it is duplicated from yii/db/ActiveQuery
* @param boolean $value whether to return the query results in terms of arrays instead of Active Records. * @param boolean $value whether to return the query results in terms of arrays instead of Active Records.
* @return ActiveQuery the query object itself * @return ActiveQuery the query object itself
*/ */
...@@ -221,8 +288,62 @@ class ActiveQuery extends \yii\base\Component ...@@ -221,8 +288,62 @@ class ActiveQuery extends \yii\base\Component
} }
/** /**
* Sets the ORDER BY part of the query.
* @param string|array $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
* (e.g. `array('id' => Query::SORT_ASC, 'name' => Query::SORT_DESC)`).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return Query the query object itself
* @see addOrderBy()
*/
public function orderBy($columns)
{
$this->orderBy = $this->normalizeOrderBy($columns);
return $this;
}
/**
* Adds additional ORDER BY columns to the query.
* @param string|array $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
* (e.g. `array('id' => Query::SORT_ASC, 'name' => Query::SORT_DESC)`).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return Query the query object itself
* @see orderBy()
*/
public function addOrderBy($columns)
{
$columns = $this->normalizeOrderBy($columns);
if ($this->orderBy === null) {
$this->orderBy = $columns;
} else {
$this->orderBy = array_merge($this->orderBy, $columns);
}
return $this;
}
protected function normalizeOrderBy($columns)
{
if (is_array($columns)) {
return $columns;
} else {
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
$result = array();
foreach ($columns as $column) {
if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
$result[$matches[1]] = strcasecmp($matches[2], 'desc') ? self::SORT_ASC : self::SORT_DESC;
} else {
$result[$column] = self::SORT_ASC;
}
}
return $result;
}
}
/**
* Sets the LIMIT part of the query. * Sets the LIMIT part of the query.
* TODO: refactor, it is duplicated from yii/db/Query
* @param integer $limit the limit * @param integer $limit the limit
* @return ActiveQuery the query object itself * @return ActiveQuery the query object itself
*/ */
...@@ -234,7 +355,6 @@ class ActiveQuery extends \yii\base\Component ...@@ -234,7 +355,6 @@ class ActiveQuery extends \yii\base\Component
/** /**
* Sets the OFFSET part of the query. * Sets the OFFSET part of the query.
* TODO: refactor, it is duplicated from yii/db/Query
* @param integer $offset the offset * @param integer $offset the offset
* @return ActiveQuery the query object itself * @return ActiveQuery the query object itself
*/ */
...@@ -264,7 +384,6 @@ class ActiveQuery extends \yii\base\Component ...@@ -264,7 +384,6 @@ class ActiveQuery extends \yii\base\Component
* ))->all(); * ))->all();
* ~~~ * ~~~
* *
* TODO: refactor, it is duplicated from yii/db/ActiveQuery
* @return ActiveQuery the query object itself * @return ActiveQuery the query object itself
*/ */
public function with() public function with()
...@@ -279,7 +398,6 @@ class ActiveQuery extends \yii\base\Component ...@@ -279,7 +398,6 @@ class ActiveQuery extends \yii\base\Component
/** /**
* Sets the [[indexBy]] property. * Sets the [[indexBy]] property.
* TODO: refactor, it is duplicated from yii/db/ActiveQuery
* @param string $column the name of the column by which the query results should be indexed by. * @param string $column the name of the column by which the query results should be indexed by.
* @return ActiveQuery the query object itself * @return ActiveQuery the query object itself
*/ */
......
...@@ -20,8 +20,6 @@ use yii\db\TableSchema; ...@@ -20,8 +20,6 @@ use yii\db\TableSchema;
/** /**
* ActiveRecord is the base class for classes representing relational data in terms of objects. * ActiveRecord is the base class for classes representing relational data in terms of objects.
* *
*
*
* @author Carsten Brandt <mail@cebe.cc> * @author Carsten Brandt <mail@cebe.cc>
* @since 2.0 * @since 2.0
*/ */
......
...@@ -13,7 +13,6 @@ namespace yii\redis; ...@@ -13,7 +13,6 @@ namespace yii\redis;
/** /**
* ActiveRecord is the base class for classes representing relational data in terms of objects. * ActiveRecord is the base class for classes representing relational data in terms of objects.
* *
*
* @author Carsten Brandt <mail@cebe.cc> * @author Carsten Brandt <mail@cebe.cc>
* @since 2.0 * @since 2.0
*/ */
......
...@@ -22,6 +22,7 @@ use yii\helpers\Inflector; ...@@ -22,6 +22,7 @@ use yii\helpers\Inflector;
* *
* @property string $driverName Name of the DB driver. This property is read-only. * @property string $driverName Name of the DB driver. This property is read-only.
* @property boolean $isActive Whether the DB connection is established. This property is read-only. * @property boolean $isActive Whether the DB connection is established. This property is read-only.
* @property LuaScriptBuilder $luaScriptBuilder This property is read-only.
* @property Transaction $transaction The currently active transaction. Null if no active transaction. This * @property Transaction $transaction The currently active transaction. Null if no active transaction. This
* property is read-only. * property is read-only.
* *
...@@ -333,6 +334,9 @@ class Connection extends Component ...@@ -333,6 +334,9 @@ class Connection extends Component
} }
} }
/**
* @return LuaScriptBuilder
*/
public function getLuaScriptBuilder() public function getLuaScriptBuilder()
{ {
return new LuaScriptBuilder(); return new LuaScriptBuilder();
......
...@@ -19,18 +19,28 @@ class LuaScriptBuilder extends \yii\base\Object ...@@ -19,18 +19,28 @@ class LuaScriptBuilder extends \yii\base\Object
{ {
public function buildAll($query) public function buildAll($query)
{ {
// TODO add support for orderBy
$modelClass = $query->modelClass; $modelClass = $query->modelClass;
$key = $modelClass::tableName(); $key = $modelClass::tableName();
return $this->build($query, "n=n+1 pks[n] = redis.call('HGETALL','$key:a:' .. pk)", 'pks'); // TODO quote return $this->build($query, "n=n+1 pks[n]=redis.call('HGETALL','$key:a:' .. pk)", 'pks'); // TODO quote
} }
public function buildOne($query) public function buildOne($query)
{ {
// TODO add support for orderBy
$modelClass = $query->modelClass; $modelClass = $query->modelClass;
$key = $modelClass::tableName(); $key = $modelClass::tableName();
return $this->build($query, "do return redis.call('HGETALL','$key:a:' .. pk) end", 'pks'); // TODO quote return $this->build($query, "do return redis.call('HGETALL','$key:a:' .. pk) end", 'pks'); // TODO quote
} }
public function buildColumn($query, $field)
{
// TODO add support for orderBy and indexBy
$modelClass = $query->modelClass;
$key = $modelClass::tableName();
return $this->build($query, "n=n+1 pks[n]=redis.call('HGET','$key:a:' .. pk,'$field')", 'pks'); // TODO quote
}
public function buildCount($query) public function buildCount($query)
{ {
return $this->build($query, 'n=n+1', 'n'); return $this->build($query, 'n=n+1', 'n');
...@@ -43,6 +53,27 @@ class LuaScriptBuilder extends \yii\base\Object ...@@ -43,6 +53,27 @@ class LuaScriptBuilder extends \yii\base\Object
return $this->build($query, "n=n+redis.call('HGET','$key:a:' .. pk,'$field')", 'n'); // TODO quote return $this->build($query, "n=n+redis.call('HGET','$key:a:' .. pk,'$field')", 'n'); // TODO quote
} }
public function buildAverage($query, $field)
{
$modelClass = $query->modelClass;
$key = $modelClass::tableName();
return $this->build($query, "n=n+1 if v==nil then v=0 end v=v+redis.call('HGET','$key:a:' .. pk,'$field')", 'v/n'); // TODO quote
}
public function buildMin($query, $field)
{
$modelClass = $query->modelClass;
$key = $modelClass::tableName();
return $this->build($query, "n=redis.call('HGET','$key:a:' .. pk,'$field') if v==nil or n<v then v=n end", 'v'); // TODO quote
}
public function buildMax($query, $field)
{
$modelClass = $query->modelClass;
$key = $modelClass::tableName();
return $this->build($query, "n=redis.call('HGET','$key:a:' .. pk,'$field') if v==nil or n>v then v=n end", 'v'); // TODO quote
}
/** /**
* @param ActiveQuery $query * @param ActiveQuery $query
*/ */
...@@ -69,6 +100,7 @@ class LuaScriptBuilder extends \yii\base\Object ...@@ -69,6 +100,7 @@ class LuaScriptBuilder extends \yii\base\Object
local allpks=redis.call('LRANGE','$key',0,-1) local allpks=redis.call('LRANGE','$key',0,-1)
local pks={} local pks={}
local n=0 local n=0
local v=nil
local i=0 local i=0
for k,pk in ipairs(allpks) do for k,pk in ipairs(allpks) do
$loadColumnValues $loadColumnValues
...@@ -100,47 +132,6 @@ EOF; ...@@ -100,47 +132,6 @@ EOF;
} }
/** /**
* @param array $columns
* @return string the GROUP BY clause
*/
public function buildGroupBy($columns)
{
return empty($columns) ? '' : 'GROUP BY ' . $this->buildColumns($columns);
}
/**
* @param string|array $condition
* @param array $params the binding parameters to be populated
* @return string the HAVING clause built from [[query]].
*/
public function buildHaving($condition, &$params)
{
$having = $this->buildCondition($condition, $params);
return $having === '' ? '' : 'HAVING ' . $having;
}
/**
* @param array $columns
* @return string the ORDER BY clause built from [[query]].
*/
public function buildOrderBy($columns)
{
if (empty($columns)) {
return '';
}
$orders = array();
foreach ($columns as $name => $direction) {
if (is_object($direction)) {
$orders[] = (string)$direction;
} else {
$orders[] = $this->db->quoteColumnName($name) . ($direction === Query::SORT_DESC ? ' DESC' : '');
}
}
return 'ORDER BY ' . implode(', ', $orders);
}
/**
* Parses the condition specification and generates the corresponding SQL expression. * Parses the condition specification and generates the corresponding SQL expression.
* @param string|array $condition the condition specification. Please refer to [[Query::where()]] * @param string|array $condition the condition specification. Please refer to [[Query::where()]]
* on how to specify a condition. * on how to specify a condition.
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace yiiunit\framework\redis; namespace yiiunit\framework\redis;
use yii\db\Query;
use yii\redis\ActiveQuery; use yii\redis\ActiveQuery;
use yiiunit\data\ar\redis\ActiveRecord; use yiiunit\data\ar\redis\ActiveRecord;
use yiiunit\data\ar\redis\Customer; use yiiunit\data\ar\redis\Customer;
...@@ -134,11 +135,10 @@ class ActiveRecordTest extends RedisTestCase ...@@ -134,11 +135,10 @@ class ActiveRecordTest extends RedisTestCase
$this->assertEquals(2, $customer->id); $this->assertEquals(2, $customer->id);
// find count, sum, average, min, max, scalar // find count, sum, average, min, max, scalar
/* $this->assertEquals(6, Customer::find()->sum('id')); $this->assertEquals(6, Customer::find()->sum('id'));
$this->assertEquals(2, Customer::find()->average('id')); $this->assertEquals(2, Customer::find()->average('id'));
$this->assertEquals(1, Customer::find()->min('id')); $this->assertEquals(1, Customer::find()->min('id'));
$this->assertEquals(3, Customer::find()->max('id')); $this->assertEquals(3, Customer::find()->max('id'));
$this->assertEquals(3, Customer::find()->select('COUNT(*)')->scalar());*/
// scope // scope
// $this->assertEquals(2, Customer::find()->active()->count()); // $this->assertEquals(2, Customer::find()->active()->count());
...@@ -227,6 +227,12 @@ class ActiveRecordTest extends RedisTestCase ...@@ -227,6 +227,12 @@ class ActiveRecordTest extends RedisTestCase
$this->assertEquals(7, OrderItem::find()->sum('quantity')); $this->assertEquals(7, OrderItem::find()->sum('quantity'));
} }
public function testFindColumn()
{
$this->assertEquals(array('user1', 'user2', 'user3'), Customer::find()->column('name'));
// TODO $this->assertEquals(array('user3', 'user2', 'user1'), Customer::find()->orderBy(array('name' => Query::SORT_DESC))->column('name'));
}
public function testExists() public function testExists()
{ {
$this->assertTrue(Customer::find()->where(array('id' => 2))->exists()); $this->assertTrue(Customer::find()->where(array('id' => 2))->exists());
......
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