Browse Source
* master: (59 commits) Refactored hasMany and hasOne so that they support cross-DBMS relationship. removed unused asset.php files fixed composer.json autoload pathes fixed c&p error allow installing yii2-dev and get the Yii.php file in the same place updated dev composer.json dependencies added composer.json for yii2-dev package fixed broken UniqueValidator removed call to nonexistsend property cleanup redis AR refactored Model and redis AR to allow drop of RecordSchema refactored redis AR to relect the latest changes "yii\swiftmailer\Mailer::createSwiftObject()" simplified. fixed empty result in findByPk list fixed problem with not closed transaction in deleteAll() fixed broken test apply changes to db\AR -> redis\AR added dependency in db\AR -> redis\AR needs to be refactored later redis AR cleanup ensure atomicity of operations ... Conflicts: tests/unit/data/config.phptags/2.0.0-beta
Carsten Brandt
11 years ago
26 changed files with 2093 additions and 191 deletions
@ -0,0 +1,102 @@ |
|||||||
|
{ |
||||||
|
"name": "yiisoft/yii2-dev", |
||||||
|
"description": "Yii2 Web Programming Framework - Development Package", |
||||||
|
"keywords": ["yii", "framework"], |
||||||
|
"homepage": "http://www.yiiframework.com/", |
||||||
|
"type": "yii2-extension", |
||||||
|
"license": "BSD-3-Clause", |
||||||
|
"authors": [ |
||||||
|
{ |
||||||
|
"name": "Qiang Xue", |
||||||
|
"email": "qiang.xue@gmail.com", |
||||||
|
"homepage": "http://www.yiiframework.com/", |
||||||
|
"role": "Founder and project lead" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Alexander Makarov", |
||||||
|
"email": "sam@rmcreative.ru", |
||||||
|
"homepage": "http://rmcreative.ru/", |
||||||
|
"role": "Core framework development" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Maurizio Domba", |
||||||
|
"homepage": "http://mdomba.info/", |
||||||
|
"role": "Core framework development" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Carsten Brandt", |
||||||
|
"email": "mail@cebe.cc", |
||||||
|
"homepage": "http://cebe.cc/", |
||||||
|
"role": "Core framework development" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Timur Ruziev", |
||||||
|
"email": "resurtm@gmail.com", |
||||||
|
"homepage": "http://resurtm.com/", |
||||||
|
"role": "Core framework development" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Paul Klimov", |
||||||
|
"email": "klimov.paul@gmail.com", |
||||||
|
"role": "Core framework development" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Wei Zhuo", |
||||||
|
"email": "weizhuo@gmail.com", |
||||||
|
"role": "Project site maintenance and development" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Sebastián Thierer", |
||||||
|
"email": "sebas@artfos.com", |
||||||
|
"role": "Component development" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Jeffrey Winesett", |
||||||
|
"email": "jefftulsa@gmail.com", |
||||||
|
"role": "Documentation and marketing" |
||||||
|
} |
||||||
|
], |
||||||
|
"support": { |
||||||
|
"issues": "https://github.com/yiisoft/yii2/issues?state=open", |
||||||
|
"forum": "http://www.yiiframework.com/forum/", |
||||||
|
"wiki": "http://www.yiiframework.com/wiki/", |
||||||
|
"irc": "irc://irc.freenode.net/yii", |
||||||
|
"source": "https://github.com/yiisoft/yii2" |
||||||
|
}, |
||||||
|
"replace": { |
||||||
|
"yiisoft/yii2-bootstrap": "self.version", |
||||||
|
"yiisoft/yii2-debug": "self.version", |
||||||
|
"yiisoft/yii2-gii": "self.version", |
||||||
|
"yiisoft/yii2-jui": "self.version", |
||||||
|
"yiisoft/yii2-smarty": "self.version", |
||||||
|
"yiisoft/yii2-swiftmailer": "self.version", |
||||||
|
"yiisoft/yii2-twig": "self.version", |
||||||
|
"yiisoft/yii2": "self.version" |
||||||
|
}, |
||||||
|
"require": { |
||||||
|
"php": ">=5.4.0", |
||||||
|
"ext-mbstring": "*", |
||||||
|
"lib-pcre": "*", |
||||||
|
"yiisoft/jquery": "1.10.*", |
||||||
|
"yiisoft/yii2-composer": "self.version", |
||||||
|
"phpspec/php-diff": ">=1.0.2", |
||||||
|
"ezyang/htmlpurifier": "4.5.*", |
||||||
|
"michelf/php-markdown": "1.3.*", |
||||||
|
"twbs/bootstrap": "3.0.*", |
||||||
|
"smarty/smarty": "*", |
||||||
|
"swiftmailer/swiftmailer": "*", |
||||||
|
"twig/twig": "*" |
||||||
|
}, |
||||||
|
"autoload": { |
||||||
|
"psr-0": { |
||||||
|
"yii\\bootstrap\\": "extensions/bootstrap/", |
||||||
|
"yii\\debug\\": "extensions/debug/", |
||||||
|
"yii\\gii\\": "extensions/gii/", |
||||||
|
"yii\\jui\\": "extensions/jui/", |
||||||
|
"yii\\smarty\\": "extensions/smarty/", |
||||||
|
"yii\\swiftmailer\\": "extensions/swiftmailer/", |
||||||
|
"yii\\twig\\": "extensions/twig/", |
||||||
|
"yii\\": "framework/yii/" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,23 +0,0 @@ |
|||||||
<?php |
|
||||||
|
|
||||||
return [ |
|
||||||
yii\jui\CoreAsset::className(), |
|
||||||
yii\jui\EffectAsset::className(), |
|
||||||
yii\jui\AccordionAsset::className(), |
|
||||||
yii\jui\AutoCompleteAsset::className(), |
|
||||||
yii\jui\ButtonAsset::className(), |
|
||||||
yii\jui\DatePickerAsset::className(), |
|
||||||
yii\jui\DatePickerRegionalAsset::className(), |
|
||||||
yii\jui\ProgressBarAsset::className(), |
|
||||||
yii\jui\ResizableAsset::className(), |
|
||||||
yii\jui\SelectableAsset::className(), |
|
||||||
yii\jui\SliderAsset::className(), |
|
||||||
yii\jui\SortableAsset::className(), |
|
||||||
yii\jui\SpinnerAsset::className(), |
|
||||||
yii\jui\TabsAsset::className(), |
|
||||||
yii\jui\TooltipAsset::className(), |
|
||||||
yii\jui\DialogAsset::className(), |
|
||||||
yii\jui\DraggableAsset::className(), |
|
||||||
yii\jui\DroppableAsset::className(), |
|
||||||
yii\jui\MenuAsset::className(), |
|
||||||
]; |
|
@ -1,11 +0,0 @@ |
|||||||
<?php |
|
||||||
|
|
||||||
return [ |
|
||||||
yii\web\YiiAsset::className(), |
|
||||||
yii\web\JqueryAsset::className(), |
|
||||||
yii\validators\PunycodeAsset::className(), |
|
||||||
yii\validators\ValidationAsset::className(), |
|
||||||
yii\widgets\ActiveFormAsset::className(), |
|
||||||
yii\captcha\CaptchaAsset::className(), |
|
||||||
yii\widgets\MaskedInputAsset::className(), |
|
||||||
]; |
|
@ -0,0 +1,382 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yii\redis; |
||||||
|
use yii\base\InvalidParamException; |
||||||
|
use yii\base\NotSupportedException; |
||||||
|
use yii\db\ActiveQueryInterface; |
||||||
|
use yii\db\ActiveQueryTrait; |
||||||
|
use yii\db\QueryTrait; |
||||||
|
|
||||||
|
/** |
||||||
|
* ActiveQuery represents a query associated with an Active Record class. |
||||||
|
* |
||||||
|
* ActiveQuery instances are usually created by [[ActiveRecord::find()]] |
||||||
|
* and [[ActiveRecord::count()]]. |
||||||
|
* |
||||||
|
* ActiveQuery mainly provides the following methods to retrieve the query results: |
||||||
|
* |
||||||
|
* - [[one()]]: returns a single record populated with the first row of data. |
||||||
|
* - [[all()]]: returns all records based on the query results. |
||||||
|
* - [[count()]]: returns the number of records. |
||||||
|
* - [[sum()]]: returns the sum over the specified column. |
||||||
|
* - [[average()]]: returns the average over the specified column. |
||||||
|
* - [[min()]]: returns the min over the specified column. |
||||||
|
* - [[max()]]: returns the max over the specified column. |
||||||
|
* - [[scalar()]]: returns the value of the first column in the first row of the query result. |
||||||
|
* - [[exists()]]: returns a value indicating whether the query result has data or not. |
||||||
|
* |
||||||
|
* You can use query methods, such as [[where()]], [[limit()]] and [[orderBy()]] to customize the query options. |
||||||
|
* |
||||||
|
* ActiveQuery also provides the following additional query options: |
||||||
|
* |
||||||
|
* - [[with()]]: list of relations that this query should be performed with. |
||||||
|
* - [[indexBy()]]: the name of the column by which the query result should be indexed. |
||||||
|
* - [[asArray()]]: whether to return each record as an array. |
||||||
|
* |
||||||
|
* These options can be configured using methods of the same name. For example: |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* $customers = Customer::find()->with('orders')->asArray()->all(); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* @author Carsten Brandt <mail@cebe.cc> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface |
||||||
|
{ |
||||||
|
use QueryTrait; |
||||||
|
use ActiveQueryTrait; |
||||||
|
|
||||||
|
/** |
||||||
|
* Executes the query and returns all results as an array. |
||||||
|
* @param Connection $db the database connection used to execute the query. |
||||||
|
* If this parameter is not given, the `db` application component will be used. |
||||||
|
* @return ActiveRecord[] the query results. If the query results in nothing, an empty array will be returned. |
||||||
|
*/ |
||||||
|
public function all($db = null) |
||||||
|
{ |
||||||
|
// TODO add support for orderBy |
||||||
|
$data = $this->executeScript($db, 'All'); |
||||||
|
$rows = []; |
||||||
|
foreach($data as $dataRow) { |
||||||
|
$row = []; |
||||||
|
$c = count($dataRow); |
||||||
|
for($i = 0; $i < $c; ) { |
||||||
|
$row[$dataRow[$i++]] = $dataRow[$i++]; |
||||||
|
} |
||||||
|
$rows[] = $row; |
||||||
|
} |
||||||
|
if (!empty($rows)) { |
||||||
|
$models = $this->createModels($rows); |
||||||
|
if (!empty($this->with)) { |
||||||
|
$this->findWith($this->with, $models); |
||||||
|
} |
||||||
|
return $models; |
||||||
|
} else { |
||||||
|
return []; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Executes the query and returns a single row of result. |
||||||
|
* @param Connection $db the database connection used to execute the query. |
||||||
|
* If this parameter is not given, the `db` application component will be used. |
||||||
|
* @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]], |
||||||
|
* the query result may be either an array or an ActiveRecord object. Null will be returned |
||||||
|
* if the query results in nothing. |
||||||
|
*/ |
||||||
|
public function one($db = null) |
||||||
|
{ |
||||||
|
// TODO add support for orderBy |
||||||
|
$data = $this->executeScript($db, 'One'); |
||||||
|
if (empty($data)) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
$row = []; |
||||||
|
$c = count($data); |
||||||
|
for($i = 0; $i < $c; ) { |
||||||
|
$row[$data[$i++]] = $data[$i++]; |
||||||
|
} |
||||||
|
if ($this->asArray) { |
||||||
|
$model = $row; |
||||||
|
} else { |
||||||
|
/** @var ActiveRecord $class */ |
||||||
|
$class = $this->modelClass; |
||||||
|
$model = $class::create($row); |
||||||
|
} |
||||||
|
if (!empty($this->with)) { |
||||||
|
$models = [$model]; |
||||||
|
$this->findWith($this->with, $models); |
||||||
|
$model = $models[0]; |
||||||
|
} |
||||||
|
return $model; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the number of records. |
||||||
|
* @param string $q the COUNT expression. This parameter is ignored by this implementation. |
||||||
|
* @param Connection $db the database connection used to execute the query. |
||||||
|
* If this parameter is not given, the `db` application component will be used. |
||||||
|
* @return integer number of records |
||||||
|
*/ |
||||||
|
public function count($q = '*', $db = null) |
||||||
|
{ |
||||||
|
if ($this->offset === null && $this->limit === null && $this->where === null) { |
||||||
|
/** @var ActiveRecord $modelClass */ |
||||||
|
$modelClass = $this->modelClass; |
||||||
|
if ($db === null) { |
||||||
|
$db = $modelClass::getDb(); |
||||||
|
} |
||||||
|
return $db->executeCommand('LLEN', [$modelClass::tableName()]); |
||||||
|
} else { |
||||||
|
return $this->executeScript($db, 'Count'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a value indicating whether the query result contains any row of data. |
||||||
|
* @param Connection $db the database connection used to execute the query. |
||||||
|
* If this parameter is not given, the `db` application component will be used. |
||||||
|
* @return boolean whether the query result contains any row of data. |
||||||
|
*/ |
||||||
|
public function exists($db = null) |
||||||
|
{ |
||||||
|
return $this->one($db) !== null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Executes the query and returns the first column of the result. |
||||||
|
* @param string $column name of the column to select |
||||||
|
* @param Connection $db the database connection used to execute the query. |
||||||
|
* If this parameter is not given, the `db` application component will be used. |
||||||
|
* @return array the first column of the query result. An empty array is returned if the query results in nothing. |
||||||
|
*/ |
||||||
|
public function column($column, $db = null) |
||||||
|
{ |
||||||
|
// TODO add support for orderBy |
||||||
|
return $this->executeScript($db, 'Column', $column); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the number of records. |
||||||
|
* @param string $column the column to sum up |
||||||
|
* @param Connection $db the database connection used to execute the query. |
||||||
|
* If this parameter is not given, the `db` application component will be used. |
||||||
|
* @return integer number of records |
||||||
|
*/ |
||||||
|
public function sum($column, $db = null) |
||||||
|
{ |
||||||
|
return $this->executeScript($db, 'Sum', $column); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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. |
||||||
|
* @param Connection $db the database connection used to execute the query. |
||||||
|
* If this parameter is not given, the `db` application component will be used. |
||||||
|
* @return integer the average of the specified column values. |
||||||
|
*/ |
||||||
|
public function average($column, $db = null) |
||||||
|
{ |
||||||
|
return $this->executeScript($db, 'Average', $column); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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. |
||||||
|
* @param Connection $db the database connection used to execute the query. |
||||||
|
* If this parameter is not given, the `db` application component will be used. |
||||||
|
* @return integer the minimum of the specified column values. |
||||||
|
*/ |
||||||
|
public function min($column, $db = null) |
||||||
|
{ |
||||||
|
return $this->executeScript($db, 'Min', $column); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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. |
||||||
|
* @param Connection $db the database connection used to execute the query. |
||||||
|
* If this parameter is not given, the `db` application component will be used. |
||||||
|
* @return integer the maximum of the specified column values. |
||||||
|
*/ |
||||||
|
public function max($column, $db = null) |
||||||
|
{ |
||||||
|
return $this->executeScript($db, 'Max', $column); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the query result as a scalar value. |
||||||
|
* 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 |
||||||
|
* @param Connection $db the database connection used to execute the query. |
||||||
|
* If this parameter is not given, the `db` application component will be used. |
||||||
|
* @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. |
||||||
|
*/ |
||||||
|
public function scalar($column, $db = null) |
||||||
|
{ |
||||||
|
$record = $this->one($db); |
||||||
|
if ($record === null) { |
||||||
|
return false; |
||||||
|
} else { |
||||||
|
return $record->$column; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Executes a script created by [[LuaScriptBuilder]] |
||||||
|
* @param Connection $db the database connection used to execute the query. |
||||||
|
* If this parameter is not given, the `db` application component will be used. |
||||||
|
* @param string $type the type of the script to generate |
||||||
|
* @param string $columnName |
||||||
|
* @return array|bool|null|string |
||||||
|
*/ |
||||||
|
protected function executeScript($db, $type, $columnName = null) |
||||||
|
{ |
||||||
|
if (!empty($this->orderBy)) { |
||||||
|
throw new NotSupportedException('orderBy is currently not supported by redis ActiveRecord.'); |
||||||
|
} |
||||||
|
|
||||||
|
/** @var ActiveRecord $modelClass */ |
||||||
|
$modelClass = $this->modelClass; |
||||||
|
|
||||||
|
if ($db === null) { |
||||||
|
$db = $modelClass::getDb(); |
||||||
|
} |
||||||
|
|
||||||
|
// find by primary key if possible. This is much faster than scanning all records |
||||||
|
if (is_array($this->where) && !isset($this->where[0]) && $modelClass::isPrimaryKey(array_keys($this->where))) { |
||||||
|
return $this->findByPk($db, $type, $columnName); |
||||||
|
} |
||||||
|
|
||||||
|
$method = 'build' . $type; |
||||||
|
$script = $db->getLuaScriptBuilder()->$method($this, $columnName); |
||||||
|
return $db->executeCommand('EVAL', [$script, 0]); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Fetch by pk if possible as this is much faster |
||||||
|
* @param Connection $db the database connection used to execute the query. |
||||||
|
* If this parameter is not given, the `db` application component will be used. |
||||||
|
* @param string $type the type of the script to generate |
||||||
|
* @param string $columnName |
||||||
|
* @return array|bool|null|string |
||||||
|
* @throws \yii\base\InvalidParamException |
||||||
|
* @throws \yii\base\NotSupportedException |
||||||
|
*/ |
||||||
|
private function findByPk($db, $type, $columnName = null) |
||||||
|
{ |
||||||
|
if (count($this->where) == 1) { |
||||||
|
$pks = (array) reset($this->where); |
||||||
|
} else { |
||||||
|
foreach($this->where as $column => $values) { |
||||||
|
if (is_array($values)) { |
||||||
|
// TODO support composite IN for composite PK |
||||||
|
throw new NotSupportedException('Find by composite PK is not supported by redis ActiveRecord.'); |
||||||
|
} |
||||||
|
} |
||||||
|
$pks = [$this->where]; |
||||||
|
} |
||||||
|
|
||||||
|
/** @var ActiveRecord $modelClass */ |
||||||
|
$modelClass = $this->modelClass; |
||||||
|
|
||||||
|
$start = $this->offset === null ? 0 : $this->offset; |
||||||
|
$i = 0; |
||||||
|
$data = []; |
||||||
|
foreach($pks as $pk) { |
||||||
|
if (++$i > $start && ($this->limit === null || $i <= $start + $this->limit)) { |
||||||
|
$key = $modelClass::tableName() . ':a:' . $modelClass::buildKey($pk); |
||||||
|
$result = $db->executeCommand('HGETALL', [$key]); |
||||||
|
if (!empty($result)) { |
||||||
|
$data[] = $result; |
||||||
|
if ($type === 'One' && $this->orderBy === null) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// TODO support orderBy |
||||||
|
|
||||||
|
switch($type) { |
||||||
|
case 'All': |
||||||
|
return $data; |
||||||
|
case 'One': |
||||||
|
return reset($data); |
||||||
|
case 'Count': |
||||||
|
return count($data); |
||||||
|
case 'Column': |
||||||
|
$column = []; |
||||||
|
foreach($data as $dataRow) { |
||||||
|
$row = []; |
||||||
|
$c = count($dataRow); |
||||||
|
for($i = 0; $i < $c; ) { |
||||||
|
$row[$dataRow[$i++]] = $dataRow[$i++]; |
||||||
|
} |
||||||
|
$column[] = $row[$columnName]; |
||||||
|
} |
||||||
|
return $column; |
||||||
|
case 'Sum': |
||||||
|
$sum = 0; |
||||||
|
foreach($data as $dataRow) { |
||||||
|
$c = count($dataRow); |
||||||
|
for($i = 0; $i < $c; ) { |
||||||
|
if ($dataRow[$i++] == $columnName) { |
||||||
|
$sum += $dataRow[$i]; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return $sum; |
||||||
|
case 'Average': |
||||||
|
$sum = 0; |
||||||
|
$count = 0; |
||||||
|
foreach($data as $dataRow) { |
||||||
|
$count++; |
||||||
|
$c = count($dataRow); |
||||||
|
for($i = 0; $i < $c; ) { |
||||||
|
if ($dataRow[$i++] == $columnName) { |
||||||
|
$sum += $dataRow[$i]; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return $sum / $count; |
||||||
|
case 'Min': |
||||||
|
$min = null; |
||||||
|
foreach($data as $dataRow) { |
||||||
|
$c = count($dataRow); |
||||||
|
for($i = 0; $i < $c; ) { |
||||||
|
if ($dataRow[$i++] == $columnName && ($min == null || $dataRow[$i] < $min)) { |
||||||
|
$min = $dataRow[$i]; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return $min; |
||||||
|
case 'Max': |
||||||
|
$max = null; |
||||||
|
foreach($data as $dataRow) { |
||||||
|
$c = count($dataRow); |
||||||
|
for($i = 0; $i < $c; ) { |
||||||
|
if ($dataRow[$i++] == $columnName && ($max == null || $dataRow[$i] > $max)) { |
||||||
|
$max = $dataRow[$i]; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return $max; |
||||||
|
} |
||||||
|
throw new InvalidParamException('Unknown fetch type: ' . $type); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,322 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yii\redis; |
||||||
|
|
||||||
|
use yii\base\InvalidConfigException; |
||||||
|
use yii\base\NotSupportedException; |
||||||
|
use yii\helpers\StringHelper; |
||||||
|
|
||||||
|
/** |
||||||
|
* ActiveRecord is the base class for classes representing relational data in terms of objects. |
||||||
|
* |
||||||
|
* This class implements the ActiveRecord pattern for the [redis](http://redis.io/) key-value store. |
||||||
|
* |
||||||
|
* For defining a record a subclass should at least implement the [[attributes()]] method to define |
||||||
|
* attributes. A primary key can be defined via [[primaryKey()]] which defaults to `id` if not specified. |
||||||
|
* |
||||||
|
* The following is an example model called `Customer`: |
||||||
|
* |
||||||
|
* ```php |
||||||
|
* class Customer extends \yii\redis\ActiveRecord |
||||||
|
* { |
||||||
|
* public function attributes() |
||||||
|
* { |
||||||
|
* return ['id', 'name', 'address', 'registration_date']; |
||||||
|
* } |
||||||
|
* } |
||||||
|
* ``` |
||||||
|
* |
||||||
|
* @author Carsten Brandt <mail@cebe.cc> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class ActiveRecord extends \yii\db\ActiveRecord |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Returns the database connection used by this AR class. |
||||||
|
* By default, the "redis" application component is used as the database connection. |
||||||
|
* You may override this method if you want to use a different database connection. |
||||||
|
* @return Connection the database connection used by this AR class. |
||||||
|
*/ |
||||||
|
public static function getDb() |
||||||
|
{ |
||||||
|
return \Yii::$app->getComponent('redis'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @inheritDoc |
||||||
|
*/ |
||||||
|
public static function createQuery() |
||||||
|
{ |
||||||
|
return new ActiveQuery(['modelClass' => get_called_class()]); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @inheritDoc |
||||||
|
*/ |
||||||
|
public static function createActiveRelation($config = []) |
||||||
|
{ |
||||||
|
return new ActiveRelation($config); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the primary key name(s) for this AR class. |
||||||
|
* This method should be overridden by child classes to define the primary key. |
||||||
|
* |
||||||
|
* Note that an array should be returned even when it is a single primary key. |
||||||
|
* |
||||||
|
* @return string[] the primary keys of this record. |
||||||
|
*/ |
||||||
|
public static function primaryKey() |
||||||
|
{ |
||||||
|
return ['id']; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the list of all attribute names of the model. |
||||||
|
* This method must be overridden by child classes to define available attributes. |
||||||
|
* @return array list of attribute names. |
||||||
|
*/ |
||||||
|
public static function attributes() |
||||||
|
{ |
||||||
|
throw new InvalidConfigException('The attributes() method of redis ActiveRecord has to be implemented by child classes.'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @inheritDocs |
||||||
|
*/ |
||||||
|
public function insert($runValidation = true, $attributes = null) |
||||||
|
{ |
||||||
|
if ($runValidation && !$this->validate($attributes)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if ($this->beforeSave(true)) { |
||||||
|
$db = static::getDb(); |
||||||
|
$values = $this->getDirtyAttributes($attributes); |
||||||
|
$pk = []; |
||||||
|
// if ($values === []) { |
||||||
|
foreach ($this->primaryKey() as $key) { |
||||||
|
$pk[$key] = $values[$key] = $this->getAttribute($key); |
||||||
|
if ($pk[$key] === null) { |
||||||
|
$pk[$key] = $values[$key] = $db->executeCommand('INCR', [static::tableName() . ':s:' . $key]); |
||||||
|
$this->setAttribute($key, $values[$key]); |
||||||
|
} |
||||||
|
} |
||||||
|
// } |
||||||
|
// save pk in a findall pool |
||||||
|
$db->executeCommand('RPUSH', [static::tableName(), static::buildKey($pk)]); |
||||||
|
|
||||||
|
$key = static::tableName() . ':a:' . static::buildKey($pk); |
||||||
|
// save attributes |
||||||
|
$args = [$key]; |
||||||
|
foreach($values as $attribute => $value) { |
||||||
|
$args[] = $attribute; |
||||||
|
$args[] = $value; |
||||||
|
} |
||||||
|
$db->executeCommand('HMSET', $args); |
||||||
|
|
||||||
|
$this->setOldAttributes($values); |
||||||
|
$this->afterSave(true); |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Updates the whole table using the provided attribute values and conditions. |
||||||
|
* For example, to change the status to be 1 for all customers whose status is 2: |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* Customer::updateAll(['status' => 1], ['id' => 2]); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* @param array $attributes attribute values (name-value pairs) to be saved into the table |
||||||
|
* @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. |
||||||
|
* Please refer to [[ActiveQuery::where()]] on how to specify this parameter. |
||||||
|
* @param array $params this parameter is ignored in redis implementation. |
||||||
|
* @return integer the number of rows updated |
||||||
|
*/ |
||||||
|
public static function updateAll($attributes, $condition = null, $params = []) |
||||||
|
{ |
||||||
|
if (empty($attributes)) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
$db = static::getDb(); |
||||||
|
$n=0; |
||||||
|
foreach(static::fetchPks($condition) as $pk) { |
||||||
|
$newPk = $pk; |
||||||
|
$pk = static::buildKey($pk); |
||||||
|
$key = static::tableName() . ':a:' . $pk; |
||||||
|
// save attributes |
||||||
|
$args = [$key]; |
||||||
|
foreach($attributes as $attribute => $value) { |
||||||
|
if (isset($newPk[$attribute])) { |
||||||
|
$newPk[$attribute] = $value; |
||||||
|
} |
||||||
|
$args[] = $attribute; |
||||||
|
$args[] = $value; |
||||||
|
} |
||||||
|
$newPk = static::buildKey($newPk); |
||||||
|
$newKey = static::tableName() . ':a:' . $newPk; |
||||||
|
// rename index if pk changed |
||||||
|
if ($newPk != $pk) { |
||||||
|
$db->executeCommand('MULTI'); |
||||||
|
$db->executeCommand('HMSET', $args); |
||||||
|
$db->executeCommand('LINSERT', [static::tableName(), 'AFTER', $pk, $newPk]); |
||||||
|
$db->executeCommand('LREM', [static::tableName(), 0, $pk]); |
||||||
|
$db->executeCommand('RENAME', [$key, $newKey]); |
||||||
|
$db->executeCommand('EXEC'); |
||||||
|
} else { |
||||||
|
$db->executeCommand('HMSET', $args); |
||||||
|
} |
||||||
|
$n++; |
||||||
|
} |
||||||
|
return $n; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Updates the whole table using the provided counter changes and conditions. |
||||||
|
* For example, to increment all customers' age by 1, |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* Customer::updateAllCounters(['age' => 1]); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* @param array $counters the counters to be updated (attribute name => increment value). |
||||||
|
* Use negative values if you want to decrement the counters. |
||||||
|
* @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. |
||||||
|
* Please refer to [[ActiveQuery::where()]] on how to specify this parameter. |
||||||
|
* @param array $params this parameter is ignored in redis implementation. |
||||||
|
* @return integer the number of rows updated |
||||||
|
*/ |
||||||
|
public static function updateAllCounters($counters, $condition = null, $params = []) |
||||||
|
{ |
||||||
|
if (empty($counters)) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
$db = static::getDb(); |
||||||
|
$n=0; |
||||||
|
foreach(static::fetchPks($condition) as $pk) { |
||||||
|
$key = static::tableName() . ':a:' . static::buildKey($pk); |
||||||
|
foreach($counters as $attribute => $value) { |
||||||
|
$db->executeCommand('HINCRBY', [$key, $attribute, $value]); |
||||||
|
} |
||||||
|
$n++; |
||||||
|
} |
||||||
|
return $n; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Deletes rows in the table using the provided conditions. |
||||||
|
* WARNING: If you do not specify any condition, this method will delete ALL rows in the table. |
||||||
|
* |
||||||
|
* For example, to delete all customers whose status is 3: |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* Customer::deleteAll(['status' => 3]); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* @param array $condition the conditions that will be put in the WHERE part of the DELETE SQL. |
||||||
|
* Please refer to [[ActiveQuery::where()]] on how to specify this parameter. |
||||||
|
* @param array $params this parameter is ignored in redis implementation. |
||||||
|
* @return integer the number of rows deleted |
||||||
|
*/ |
||||||
|
public static function deleteAll($condition = null, $params = []) |
||||||
|
{ |
||||||
|
$db = static::getDb(); |
||||||
|
$attributeKeys = []; |
||||||
|
$pks = static::fetchPks($condition); |
||||||
|
$db->executeCommand('MULTI'); |
||||||
|
foreach($pks as $pk) { |
||||||
|
$pk = static::buildKey($pk); |
||||||
|
$db->executeCommand('LREM', [static::tableName(), 0, $pk]); |
||||||
|
$attributeKeys[] = static::tableName() . ':a:' . $pk; |
||||||
|
} |
||||||
|
if (empty($attributeKeys)) { |
||||||
|
$db->executeCommand('EXEC'); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
$db->executeCommand('DEL', $attributeKeys); |
||||||
|
$result = $db->executeCommand('EXEC'); |
||||||
|
return end($result); |
||||||
|
} |
||||||
|
|
||||||
|
private static function fetchPks($condition) |
||||||
|
{ |
||||||
|
$query = static::createQuery(); |
||||||
|
$query->where($condition); |
||||||
|
$records = $query->asArray()->all(); // TODO limit fetched columns to pk |
||||||
|
$primaryKey = static::primaryKey(); |
||||||
|
|
||||||
|
$pks = []; |
||||||
|
foreach($records as $record) { |
||||||
|
$pk = []; |
||||||
|
foreach($primaryKey as $key) { |
||||||
|
$pk[$key] = $record[$key]; |
||||||
|
} |
||||||
|
$pks[] = $pk; |
||||||
|
} |
||||||
|
return $pks; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builds a normalized key from a given primary key value. |
||||||
|
* |
||||||
|
* @param mixed $key the key to be normalized |
||||||
|
* @return string the generated key |
||||||
|
*/ |
||||||
|
public static function buildKey($key) |
||||||
|
{ |
||||||
|
if (is_numeric($key)) { |
||||||
|
return $key; |
||||||
|
} elseif (is_string($key)) { |
||||||
|
return ctype_alnum($key) && StringHelper::strlen($key) <= 32 ? $key : md5($key); |
||||||
|
} elseif (is_array($key)) { |
||||||
|
if (count($key) == 1) { |
||||||
|
return self::buildKey(reset($key)); |
||||||
|
} |
||||||
|
ksort($key); // ensure order is always the same |
||||||
|
$isNumeric = true; |
||||||
|
foreach($key as $value) { |
||||||
|
if (!is_numeric($value)) { |
||||||
|
$isNumeric = false; |
||||||
|
} |
||||||
|
} |
||||||
|
if ($isNumeric) { |
||||||
|
return implode('-', $key); |
||||||
|
} |
||||||
|
} |
||||||
|
return md5(json_encode($key)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @inheritdoc |
||||||
|
*/ |
||||||
|
public static function getTableSchema() |
||||||
|
{ |
||||||
|
throw new NotSupportedException('getTableSchema() is not supported by redis ActiveRecord'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @inheritdoc |
||||||
|
*/ |
||||||
|
public static function findBySql($sql, $params = []) |
||||||
|
{ |
||||||
|
throw new NotSupportedException('findBySql() is not supported by redis ActiveRecord'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a value indicating whether the specified operation is transactional in the current [[scenario]]. |
||||||
|
* This method will always return false as transactional operations are not supported by redis. |
||||||
|
* @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]]. |
||||||
|
* @return boolean whether the specified operation is transactional in the current [[scenario]]. |
||||||
|
*/ |
||||||
|
public function isTransactional($operation) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,67 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yii\redis; |
||||||
|
|
||||||
|
use yii\db\ActiveRelationInterface; |
||||||
|
use yii\db\ActiveRelationTrait; |
||||||
|
|
||||||
|
/** |
||||||
|
* ActiveRelation represents a relation between two Active Record classes. |
||||||
|
* |
||||||
|
* ActiveRelation instances are usually created by calling [[ActiveRecord::hasOne()]] and |
||||||
|
* [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining |
||||||
|
* a getter method which calls one of the above methods and returns the created ActiveRelation object. |
||||||
|
* |
||||||
|
* A relation is specified by [[link]] which represents the association between columns |
||||||
|
* of different tables; and the multiplicity of the relation is indicated by [[multiple]]. |
||||||
|
* |
||||||
|
* If a relation involves a pivot table, it may be specified by [[via()]] or [[viaTable()]] method. |
||||||
|
* |
||||||
|
* @author Carsten Brandt <mail@cebe.cc> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class ActiveRelation extends ActiveQuery implements ActiveRelationInterface |
||||||
|
{ |
||||||
|
use ActiveRelationTrait; |
||||||
|
|
||||||
|
/** |
||||||
|
* Executes a script created by [[LuaScriptBuilder]] |
||||||
|
* @param Connection $db the database connection used to execute the query. |
||||||
|
* If this parameter is not given, the `db` application component will be used. |
||||||
|
* @param string $type the type of the script to generate |
||||||
|
* @param null $column |
||||||
|
* @return array|bool|null|string |
||||||
|
*/ |
||||||
|
protected function executeScript($db, $type, $column=null) |
||||||
|
{ |
||||||
|
if ($this->primaryModel !== null) { |
||||||
|
// lazy loading |
||||||
|
if ($this->via instanceof self) { |
||||||
|
// via pivot table |
||||||
|
$viaModels = $this->via->findPivotRows([$this->primaryModel]); |
||||||
|
$this->filterByModels($viaModels); |
||||||
|
} elseif (is_array($this->via)) { |
||||||
|
// via relation |
||||||
|
/** @var ActiveRelation $viaQuery */ |
||||||
|
list($viaName, $viaQuery) = $this->via; |
||||||
|
if ($viaQuery->multiple) { |
||||||
|
$viaModels = $viaQuery->all(); |
||||||
|
$this->primaryModel->populateRelation($viaName, $viaModels); |
||||||
|
} else { |
||||||
|
$model = $viaQuery->one(); |
||||||
|
$this->primaryModel->populateRelation($viaName, $model); |
||||||
|
$viaModels = $model === null ? [] : [$model]; |
||||||
|
} |
||||||
|
$this->filterByModels($viaModels); |
||||||
|
} else { |
||||||
|
$this->filterByModels([$this->primaryModel]); |
||||||
|
} |
||||||
|
} |
||||||
|
return parent::executeScript($db, $type, $column); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,365 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yii\redis; |
||||||
|
|
||||||
|
use yii\base\NotSupportedException; |
||||||
|
use yii\db\Exception; |
||||||
|
use yii\db\Expression; |
||||||
|
|
||||||
|
/** |
||||||
|
* LuaScriptBuilder builds lua scripts used for retrieving data from redis. |
||||||
|
* |
||||||
|
* @author Carsten Brandt <mail@cebe.cc> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class LuaScriptBuilder extends \yii\base\Object |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Builds a Lua script for finding a list of records |
||||||
|
* @param ActiveQuery $query the query used to build the script |
||||||
|
* @return string |
||||||
|
*/ |
||||||
|
public function buildAll($query) |
||||||
|
{ |
||||||
|
// TODO add support for orderBy |
||||||
|
/** @var ActiveRecord $modelClass */ |
||||||
|
$modelClass = $query->modelClass; |
||||||
|
$key = $this->quoteValue($modelClass::tableName() . ':a:'); |
||||||
|
return $this->build($query, "n=n+1 pks[n]=redis.call('HGETALL',$key .. pk)", 'pks'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builds a Lua script for finding one record |
||||||
|
* @param ActiveQuery $query the query used to build the script |
||||||
|
* @return string |
||||||
|
*/ |
||||||
|
public function buildOne($query) |
||||||
|
{ |
||||||
|
// TODO add support for orderBy |
||||||
|
/** @var ActiveRecord $modelClass */ |
||||||
|
$modelClass = $query->modelClass; |
||||||
|
$key = $this->quoteValue($modelClass::tableName() . ':a:'); |
||||||
|
return $this->build($query, "do return redis.call('HGETALL',$key .. pk) end", 'pks'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builds a Lua script for finding a column |
||||||
|
* @param ActiveQuery $query the query used to build the script |
||||||
|
* @param string $column name of the column |
||||||
|
* @return string |
||||||
|
*/ |
||||||
|
public function buildColumn($query, $column) |
||||||
|
{ |
||||||
|
// TODO add support for orderBy and indexBy |
||||||
|
/** @var ActiveRecord $modelClass */ |
||||||
|
$modelClass = $query->modelClass; |
||||||
|
$key = $this->quoteValue($modelClass::tableName() . ':a:'); |
||||||
|
return $this->build($query, "n=n+1 pks[n]=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'pks'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builds a Lua script for getting count of records |
||||||
|
* @param ActiveQuery $query the query used to build the script |
||||||
|
* @return string |
||||||
|
*/ |
||||||
|
public function buildCount($query) |
||||||
|
{ |
||||||
|
return $this->build($query, 'n=n+1', 'n'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builds a Lua script for finding the sum of a column |
||||||
|
* @param ActiveQuery $query the query used to build the script |
||||||
|
* @param string $column name of the column |
||||||
|
* @return string |
||||||
|
*/ |
||||||
|
public function buildSum($query, $column) |
||||||
|
{ |
||||||
|
/** @var ActiveRecord $modelClass */ |
||||||
|
$modelClass = $query->modelClass; |
||||||
|
$key = $this->quoteValue($modelClass::tableName() . ':a:'); |
||||||
|
return $this->build($query, "n=n+redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'n'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builds a Lua script for finding the average of a column |
||||||
|
* @param ActiveQuery $query the query used to build the script |
||||||
|
* @param string $column name of the column |
||||||
|
* @return string |
||||||
|
*/ |
||||||
|
public function buildAverage($query, $column) |
||||||
|
{ |
||||||
|
/** @var ActiveRecord $modelClass */ |
||||||
|
$modelClass = $query->modelClass; |
||||||
|
$key = $this->quoteValue($modelClass::tableName() . ':a:'); |
||||||
|
return $this->build($query, "n=n+1 if v==nil then v=0 end v=v+redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'v/n'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builds a Lua script for finding the min value of a column |
||||||
|
* @param ActiveQuery $query the query used to build the script |
||||||
|
* @param string $column name of the column |
||||||
|
* @return string |
||||||
|
*/ |
||||||
|
public function buildMin($query, $column) |
||||||
|
{ |
||||||
|
/** @var ActiveRecord $modelClass */ |
||||||
|
$modelClass = $query->modelClass; |
||||||
|
$key = $this->quoteValue($modelClass::tableName() . ':a:'); |
||||||
|
return $this->build($query, "n=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ") if v==nil or n<v then v=n end", 'v'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builds a Lua script for finding the max value of a column |
||||||
|
* @param ActiveQuery $query the query used to build the script |
||||||
|
* @param string $column name of the column |
||||||
|
* @return string |
||||||
|
*/ |
||||||
|
public function buildMax($query, $column) |
||||||
|
{ |
||||||
|
/** @var ActiveRecord $modelClass */ |
||||||
|
$modelClass = $query->modelClass; |
||||||
|
$key = $this->quoteValue($modelClass::tableName() . ':a:'); |
||||||
|
return $this->build($query, "n=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ") if v==nil or n>v then v=n end", 'v'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param ActiveQuery $query the query used to build the script |
||||||
|
* @param string $buildResult the lua script for building the result |
||||||
|
* @param string $return the lua variable that should be returned |
||||||
|
* @return string |
||||||
|
*/ |
||||||
|
private function build($query, $buildResult, $return) |
||||||
|
{ |
||||||
|
if (!empty($query->orderBy)) { |
||||||
|
throw new NotSupportedException('orderBy is currently not supported by redis ActiveRecord.'); |
||||||
|
} |
||||||
|
|
||||||
|
$columns = []; |
||||||
|
if ($query->where !== null) { |
||||||
|
$condition = $this->buildCondition($query->where, $columns); |
||||||
|
} else { |
||||||
|
$condition = 'true'; |
||||||
|
} |
||||||
|
|
||||||
|
$start = $query->offset === null ? 0 : $query->offset; |
||||||
|
$limitCondition = 'i>' . $start . ($query->limit === null ? '' : ' and i<=' . ($start + $query->limit)); |
||||||
|
|
||||||
|
/** @var ActiveRecord $modelClass */ |
||||||
|
$modelClass = $query->modelClass; |
||||||
|
$key = $this->quoteValue($modelClass::tableName()); |
||||||
|
$loadColumnValues = ''; |
||||||
|
foreach($columns as $column => $alias) { |
||||||
|
$loadColumnValues .= "local $alias=redis.call('HGET',$key .. ':a:' .. pk, '$column')\n"; |
||||||
|
} |
||||||
|
|
||||||
|
return <<<EOF |
||||||
|
local allpks=redis.call('LRANGE',$key,0,-1) |
||||||
|
local pks={} |
||||||
|
local n=0 |
||||||
|
local v=nil |
||||||
|
local i=0 |
||||||
|
for k,pk in ipairs(allpks) do |
||||||
|
$loadColumnValues |
||||||
|
if $condition then |
||||||
|
i=i+1 |
||||||
|
if $limitCondition then |
||||||
|
$buildResult |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
return $return |
||||||
|
EOF; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds a column to the list of columns to retrieve and creates an alias |
||||||
|
* @param string $column the column name to add |
||||||
|
* @param array $columns list of columns given by reference |
||||||
|
* @return string the alias generated for the column name |
||||||
|
*/ |
||||||
|
private function addColumn($column, &$columns) |
||||||
|
{ |
||||||
|
if (isset($columns[$column])) { |
||||||
|
return $columns[$column]; |
||||||
|
} |
||||||
|
$name = 'c' . preg_replace("/[^A-z]+/", "", $column) . count($columns); |
||||||
|
return $columns[$column] = $name; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Quotes a string value for use in a query. |
||||||
|
* Note that if the parameter is not a string or int, it will be returned without change. |
||||||
|
* @param string $str string to be quoted |
||||||
|
* @return string the properly quoted string |
||||||
|
*/ |
||||||
|
private function quoteValue($str) |
||||||
|
{ |
||||||
|
if (!is_string($str) && !is_int($str)) { |
||||||
|
return $str; |
||||||
|
} |
||||||
|
|
||||||
|
return "'" . addcslashes(str_replace("'", "\\'", $str), "\000\n\r\\\032") . "'"; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Parses the condition specification and generates the corresponding Lua expression. |
||||||
|
* @param string|array $condition the condition specification. Please refer to [[ActiveQuery::where()]] |
||||||
|
* on how to specify a condition. |
||||||
|
* @param array $columns the list of columns and aliases to be used |
||||||
|
* @return string the generated SQL expression |
||||||
|
* @throws \yii\db\Exception if the condition is in bad format |
||||||
|
* @throws \yii\base\NotSupportedException if the condition is not an array |
||||||
|
*/ |
||||||
|
public function buildCondition($condition, &$columns) |
||||||
|
{ |
||||||
|
static $builders = [ |
||||||
|
'and' => 'buildAndCondition', |
||||||
|
'or' => 'buildAndCondition', |
||||||
|
'between' => 'buildBetweenCondition', |
||||||
|
'not between' => 'buildBetweenCondition', |
||||||
|
'in' => 'buildInCondition', |
||||||
|
'not in' => 'buildInCondition', |
||||||
|
'like' => 'buildLikeCondition', |
||||||
|
'not like' => 'buildLikeCondition', |
||||||
|
'or like' => 'buildLikeCondition', |
||||||
|
'or not like' => 'buildLikeCondition', |
||||||
|
]; |
||||||
|
|
||||||
|
if (!is_array($condition)) { |
||||||
|
throw new NotSupportedException('Where condition must be an array in redis ActiveRecord.'); |
||||||
|
} |
||||||
|
if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ... |
||||||
|
$operator = strtolower($condition[0]); |
||||||
|
if (isset($builders[$operator])) { |
||||||
|
$method = $builders[$operator]; |
||||||
|
array_shift($condition); |
||||||
|
return $this->$method($operator, $condition, $columns); |
||||||
|
} else { |
||||||
|
throw new Exception('Found unknown operator in query: ' . $operator); |
||||||
|
} |
||||||
|
} else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ... |
||||||
|
return $this->buildHashCondition($condition, $columns); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private function buildHashCondition($condition, &$columns) |
||||||
|
{ |
||||||
|
$parts = []; |
||||||
|
foreach ($condition as $column => $value) { |
||||||
|
if (is_array($value)) { // IN condition |
||||||
|
$parts[] = $this->buildInCondition('in', [$column, $value], $columns); |
||||||
|
} else { |
||||||
|
$column = $this->addColumn($column, $columns); |
||||||
|
if ($value === null) { |
||||||
|
$parts[] = "$column==nil"; |
||||||
|
} elseif ($value instanceof Expression) { |
||||||
|
$parts[] = "$column==" . $value->expression; |
||||||
|
} else { |
||||||
|
$value = $this->quoteValue($value); |
||||||
|
$parts[] = "$column==$value"; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return count($parts) === 1 ? $parts[0] : '(' . implode(') and (', $parts) . ')'; |
||||||
|
} |
||||||
|
|
||||||
|
private function buildAndCondition($operator, $operands, &$columns) |
||||||
|
{ |
||||||
|
$parts = []; |
||||||
|
foreach ($operands as $operand) { |
||||||
|
if (is_array($operand)) { |
||||||
|
$operand = $this->buildCondition($operand, $columns); |
||||||
|
} |
||||||
|
if ($operand !== '') { |
||||||
|
$parts[] = $operand; |
||||||
|
} |
||||||
|
} |
||||||
|
if (!empty($parts)) { |
||||||
|
return '(' . implode(") $operator (", $parts) . ')'; |
||||||
|
} else { |
||||||
|
return ''; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private function buildBetweenCondition($operator, $operands, &$columns) |
||||||
|
{ |
||||||
|
if (!isset($operands[0], $operands[1], $operands[2])) { |
||||||
|
throw new Exception("Operator '$operator' requires three operands."); |
||||||
|
} |
||||||
|
|
||||||
|
list($column, $value1, $value2) = $operands; |
||||||
|
|
||||||
|
$value1 = $this->quoteValue($value1); |
||||||
|
$value2 = $this->quoteValue($value2); |
||||||
|
$column = $this->addColumn($column, $columns); |
||||||
|
return "$column >= $value1 and $column <= $value2"; |
||||||
|
} |
||||||
|
|
||||||
|
private function buildInCondition($operator, $operands, &$columns) |
||||||
|
{ |
||||||
|
if (!isset($operands[0], $operands[1])) { |
||||||
|
throw new Exception("Operator '$operator' requires two operands."); |
||||||
|
} |
||||||
|
|
||||||
|
list($column, $values) = $operands; |
||||||
|
|
||||||
|
$values = (array)$values; |
||||||
|
|
||||||
|
if (empty($values) || $column === []) { |
||||||
|
return $operator === 'in' ? 'false' : 'true'; |
||||||
|
} |
||||||
|
|
||||||
|
if (count($column) > 1) { |
||||||
|
return $this->buildCompositeInCondition($operator, $column, $values, $columns); |
||||||
|
} elseif (is_array($column)) { |
||||||
|
$column = reset($column); |
||||||
|
} |
||||||
|
$columnAlias = $this->addColumn($column, $columns); |
||||||
|
$parts = []; |
||||||
|
foreach ($values as $i => $value) { |
||||||
|
if (is_array($value)) { |
||||||
|
$value = isset($value[$column]) ? $value[$column] : null; |
||||||
|
} |
||||||
|
if ($value === null) { |
||||||
|
$parts[] = "$columnAlias==nil"; |
||||||
|
} elseif ($value instanceof Expression) { |
||||||
|
$parts[] = "$columnAlias==" . $value->expression; |
||||||
|
} else { |
||||||
|
$value = $this->quoteValue($value); |
||||||
|
$parts[] = "$columnAlias==$value"; |
||||||
|
} |
||||||
|
} |
||||||
|
$operator = $operator === 'in' ? '' : 'not '; |
||||||
|
return "$operator(" . implode(' or ', $parts) . ')'; |
||||||
|
} |
||||||
|
|
||||||
|
protected function buildCompositeInCondition($operator, $inColumns, $values, &$columns) |
||||||
|
{ |
||||||
|
$vss = []; |
||||||
|
foreach ($values as $value) { |
||||||
|
$vs = []; |
||||||
|
foreach ($inColumns as $column) { |
||||||
|
$column = $this->addColumn($column, $columns); |
||||||
|
if (isset($value[$column])) { |
||||||
|
$vs[] = "$column==" . $this->quoteValue($value[$column]); |
||||||
|
} else { |
||||||
|
$vs[] = "$column==nil"; |
||||||
|
} |
||||||
|
} |
||||||
|
$vss[] = '(' . implode(' and ', $vs) . ')'; |
||||||
|
} |
||||||
|
$operator = $operator === 'in' ? '' : 'not '; |
||||||
|
return "$operator(" . implode(' or ', $vss) . ')'; |
||||||
|
} |
||||||
|
|
||||||
|
private function buildLikeCondition($operator, $operands, &$columns) |
||||||
|
{ |
||||||
|
throw new NotSupportedException('LIKE conditions are not suppoerted by redis ActiveRecord.'); |
||||||
|
} |
||||||
|
} |
@ -1,93 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* Transaction class file. |
|
||||||
* |
|
||||||
* @link http://www.yiiframework.com/ |
|
||||||
* @copyright Copyright © 2008 Yii Software LLC |
|
||||||
* @license http://www.yiiframework.com/license/ |
|
||||||
*/ |
|
||||||
|
|
||||||
namespace yii\redis; |
|
||||||
|
|
||||||
use yii\base\InvalidConfigException; |
|
||||||
use yii\db\Exception; |
|
||||||
|
|
||||||
/** |
|
||||||
* Transaction represents a DB transaction. |
|
||||||
* |
|
||||||
* @property boolean $isActive Whether this transaction is active. Only an active transaction can [[commit()]] |
|
||||||
* or [[rollBack()]]. This property is read-only. |
|
||||||
* |
|
||||||
* @author Carsten Brandt <mail@cebe.cc> |
|
||||||
* @since 2.0 |
|
||||||
*/ |
|
||||||
class Transaction extends \yii\base\Object |
|
||||||
{ |
|
||||||
/** |
|
||||||
* @var Connection the database connection that this transaction is associated with. |
|
||||||
*/ |
|
||||||
public $db; |
|
||||||
/** |
|
||||||
* @var boolean whether this transaction is active. Only an active transaction |
|
||||||
* can [[commit()]] or [[rollBack()]]. This property is set true when the transaction is started. |
|
||||||
*/ |
|
||||||
private $_active = false; |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns a value indicating whether this transaction is active. |
|
||||||
* @return boolean whether this transaction is active. Only an active transaction |
|
||||||
* can [[commit()]] or [[rollBack()]]. |
|
||||||
*/ |
|
||||||
public function getIsActive() |
|
||||||
{ |
|
||||||
return $this->_active; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Begins a transaction. |
|
||||||
* @throws InvalidConfigException if [[connection]] is null |
|
||||||
*/ |
|
||||||
public function begin() |
|
||||||
{ |
|
||||||
if (!$this->_active) { |
|
||||||
if ($this->db === null) { |
|
||||||
throw new InvalidConfigException('Transaction::db must be set.'); |
|
||||||
} |
|
||||||
\Yii::trace('Starting transaction', __CLASS__); |
|
||||||
$this->db->open(); |
|
||||||
$this->db->createCommand('MULTI')->execute(); |
|
||||||
$this->_active = true; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Commits a transaction. |
|
||||||
* @throws Exception if the transaction or the DB connection is not active. |
|
||||||
*/ |
|
||||||
public function commit() |
|
||||||
{ |
|
||||||
if ($this->_active && $this->db && $this->db->isActive) { |
|
||||||
\Yii::trace('Committing transaction', __CLASS__); |
|
||||||
$this->db->createCommand('EXEC')->execute(); |
|
||||||
// TODO handle result of EXEC |
|
||||||
$this->_active = false; |
|
||||||
} else { |
|
||||||
throw new Exception('Failed to commit transaction: transaction was inactive.'); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Rolls back a transaction. |
|
||||||
* @throws Exception if the transaction or the DB connection is not active. |
|
||||||
*/ |
|
||||||
public function rollback() |
|
||||||
{ |
|
||||||
if ($this->_active && $this->db && $this->db->isActive) { |
|
||||||
\Yii::trace('Rolling back transaction', __CLASS__); |
|
||||||
$this->db->pdo->commit(); |
|
||||||
$this->_active = false; |
|
||||||
} else { |
|
||||||
throw new Exception('Failed to roll back transaction: transaction was inactive.'); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,26 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yiiunit\data\ar\redis; |
||||||
|
|
||||||
|
use yii\redis\Connection; |
||||||
|
|
||||||
|
/** |
||||||
|
* ActiveRecord is ... |
||||||
|
* |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class ActiveRecord extends \yii\redis\ActiveRecord |
||||||
|
{ |
||||||
|
public static $db; |
||||||
|
|
||||||
|
public static function getDb() |
||||||
|
{ |
||||||
|
return self::$db; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\data\ar\redis; |
||||||
|
|
||||||
|
class Customer extends ActiveRecord |
||||||
|
{ |
||||||
|
const STATUS_ACTIVE = 1; |
||||||
|
const STATUS_INACTIVE = 2; |
||||||
|
|
||||||
|
public $status2; |
||||||
|
|
||||||
|
public static function attributes() |
||||||
|
{ |
||||||
|
return ['id', 'email', 'name', 'address', 'status']; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return \yii\redis\ActiveRelation |
||||||
|
*/ |
||||||
|
public function getOrders() |
||||||
|
{ |
||||||
|
return $this->hasMany(Order::className(), ['customer_id' => 'id']); |
||||||
|
} |
||||||
|
|
||||||
|
public static function active($query) |
||||||
|
{ |
||||||
|
$query->andWhere(['status' => 1]); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\data\ar\redis; |
||||||
|
|
||||||
|
class Item extends ActiveRecord |
||||||
|
{ |
||||||
|
public static function attributes() |
||||||
|
{ |
||||||
|
return ['id', 'name', 'category_id']; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\data\ar\redis; |
||||||
|
|
||||||
|
class Order extends ActiveRecord |
||||||
|
{ |
||||||
|
public static function attributes() |
||||||
|
{ |
||||||
|
return ['id', 'customer_id', 'create_time', 'total']; |
||||||
|
} |
||||||
|
|
||||||
|
public function getCustomer() |
||||||
|
{ |
||||||
|
return $this->hasOne(Customer::className(), ['id' => 'customer_id']); |
||||||
|
} |
||||||
|
|
||||||
|
public function getOrderItems() |
||||||
|
{ |
||||||
|
return $this->hasMany(OrderItem::className(), ['order_id' => 'id']); |
||||||
|
} |
||||||
|
|
||||||
|
public function getItems() |
||||||
|
{ |
||||||
|
return $this->hasMany(Item::className(), ['id' => 'item_id']) |
||||||
|
->via('orderItems', function($q) { |
||||||
|
// additional query configuration |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public function getBooks() |
||||||
|
{ |
||||||
|
return $this->hasMany(Item::className(), ['id' => 'item_id']) |
||||||
|
->via('orderItems', ['order_id' => 'id']); |
||||||
|
//->where(['category_id' => 1]); |
||||||
|
} |
||||||
|
|
||||||
|
public function beforeSave($insert) |
||||||
|
{ |
||||||
|
if (parent::beforeSave($insert)) { |
||||||
|
$this->create_time = time(); |
||||||
|
return true; |
||||||
|
} else { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\data\ar\redis; |
||||||
|
|
||||||
|
use yii\redis\RecordSchema; |
||||||
|
|
||||||
|
class OrderItem extends ActiveRecord |
||||||
|
{ |
||||||
|
public static function primaryKey() |
||||||
|
{ |
||||||
|
return ['order_id', 'item_id']; |
||||||
|
} |
||||||
|
|
||||||
|
public static function attributes() |
||||||
|
{ |
||||||
|
return ['order_id', 'item_id', 'quantity', 'subtotal']; |
||||||
|
} |
||||||
|
|
||||||
|
public function getOrder() |
||||||
|
{ |
||||||
|
return $this->hasOne(Order::className(), ['id' => 'order_id']); |
||||||
|
} |
||||||
|
|
||||||
|
public function getItem() |
||||||
|
{ |
||||||
|
return $this->hasOne(Item::className(), ['id' => 'item_id']); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,466 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\framework\redis; |
||||||
|
|
||||||
|
use yii\db\Query; |
||||||
|
use yii\redis\ActiveQuery; |
||||||
|
use yiiunit\data\ar\redis\ActiveRecord; |
||||||
|
use yiiunit\data\ar\redis\Customer; |
||||||
|
use yiiunit\data\ar\redis\OrderItem; |
||||||
|
use yiiunit\data\ar\redis\Order; |
||||||
|
use yiiunit\data\ar\redis\Item; |
||||||
|
|
||||||
|
/** |
||||||
|
* @group redis |
||||||
|
*/ |
||||||
|
class ActiveRecordTest extends RedisTestCase |
||||||
|
{ |
||||||
|
public function setUp() |
||||||
|
{ |
||||||
|
parent::setUp(); |
||||||
|
ActiveRecord::$db = $this->getConnection(); |
||||||
|
|
||||||
|
$customer = new Customer(); |
||||||
|
$customer->setAttributes(['email' => 'user1@example.com', 'name' => 'user1', 'address' => 'address1', 'status' => 1], false); |
||||||
|
$customer->save(false); |
||||||
|
$customer = new Customer(); |
||||||
|
$customer->setAttributes(['email' => 'user2@example.com', 'name' => 'user2', 'address' => 'address2', 'status' => 1], false); |
||||||
|
$customer->save(false); |
||||||
|
$customer = new Customer(); |
||||||
|
$customer->setAttributes(['email' => 'user3@example.com', 'name' => 'user3', 'address' => 'address3', 'status' => 2], false); |
||||||
|
$customer->save(false); |
||||||
|
|
||||||
|
// INSERT INTO tbl_category (name) VALUES ('Books'); |
||||||
|
// INSERT INTO tbl_category (name) VALUES ('Movies'); |
||||||
|
|
||||||
|
$item = new Item(); |
||||||
|
$item->setAttributes(['name' => 'Agile Web Application Development with Yii1.1 and PHP5', 'category_id' => 1], false); |
||||||
|
$item->save(false); |
||||||
|
$item = new Item(); |
||||||
|
$item->setAttributes(['name' => 'Yii 1.1 Application Development Cookbook', 'category_id' => 1], false); |
||||||
|
$item->save(false); |
||||||
|
$item = new Item(); |
||||||
|
$item->setAttributes(['name' => 'Ice Age', 'category_id' => 2], false); |
||||||
|
$item->save(false); |
||||||
|
$item = new Item(); |
||||||
|
$item->setAttributes(['name' => 'Toy Story', 'category_id' => 2], false); |
||||||
|
$item->save(false); |
||||||
|
$item = new Item(); |
||||||
|
$item->setAttributes(['name' => 'Cars', 'category_id' => 2], false); |
||||||
|
$item->save(false); |
||||||
|
|
||||||
|
$order = new Order(); |
||||||
|
$order->setAttributes(['customer_id' => 1, 'create_time' => 1325282384, 'total' => 110.0], false); |
||||||
|
$order->save(false); |
||||||
|
$order = new Order(); |
||||||
|
$order->setAttributes(['customer_id' => 2, 'create_time' => 1325334482, 'total' => 33.0], false); |
||||||
|
$order->save(false); |
||||||
|
$order = new Order(); |
||||||
|
$order->setAttributes(['customer_id' => 2, 'create_time' => 1325502201, 'total' => 40.0], false); |
||||||
|
$order->save(false); |
||||||
|
|
||||||
|
$orderItem = new OrderItem(); |
||||||
|
$orderItem->setAttributes(['order_id' => 1, 'item_id' => 1, 'quantity' => 1, 'subtotal' => 30.0], false); |
||||||
|
$orderItem->save(false); |
||||||
|
$orderItem = new OrderItem(); |
||||||
|
$orderItem->setAttributes(['order_id' => 1, 'item_id' => 2, 'quantity' => 2, 'subtotal' => 40.0], false); |
||||||
|
$orderItem->save(false); |
||||||
|
$orderItem = new OrderItem(); |
||||||
|
$orderItem->setAttributes(['order_id' => 2, 'item_id' => 4, 'quantity' => 1, 'subtotal' => 10.0], false); |
||||||
|
$orderItem->save(false); |
||||||
|
$orderItem = new OrderItem(); |
||||||
|
$orderItem->setAttributes(['order_id' => 2, 'item_id' => 5, 'quantity' => 1, 'subtotal' => 15.0], false); |
||||||
|
$orderItem->save(false); |
||||||
|
$orderItem = new OrderItem(); |
||||||
|
$orderItem->setAttributes(['order_id' => 2, 'item_id' => 3, 'quantity' => 1, 'subtotal' => 8.0], false); |
||||||
|
$orderItem->save(false); |
||||||
|
$orderItem = new OrderItem(); |
||||||
|
$orderItem->setAttributes(['order_id' => 3, 'item_id' => 2, 'quantity' => 1, 'subtotal' => 40.0], false); |
||||||
|
$orderItem->save(false); |
||||||
|
} |
||||||
|
|
||||||
|
public function testFind() |
||||||
|
{ |
||||||
|
// find one |
||||||
|
$result = Customer::find(); |
||||||
|
$this->assertTrue($result instanceof ActiveQuery); |
||||||
|
$customer = $result->one(); |
||||||
|
$this->assertTrue($customer instanceof Customer); |
||||||
|
|
||||||
|
// find all |
||||||
|
$customers = Customer::find()->all(); |
||||||
|
$this->assertEquals(3, count($customers)); |
||||||
|
$this->assertTrue($customers[0] instanceof Customer); |
||||||
|
$this->assertTrue($customers[1] instanceof Customer); |
||||||
|
$this->assertTrue($customers[2] instanceof Customer); |
||||||
|
|
||||||
|
// find by a single primary key |
||||||
|
$customer = Customer::find(2); |
||||||
|
$this->assertTrue($customer instanceof Customer); |
||||||
|
$this->assertEquals('user2', $customer->name); |
||||||
|
$customer = Customer::find(5); |
||||||
|
$this->assertNull($customer); |
||||||
|
$customer = Customer::find(['id' => [5, 6, 1]]); |
||||||
|
$this->assertEquals(1, count($customer)); |
||||||
|
$customer = Customer::find()->where(['id' => [5, 6, 1]])->one(); |
||||||
|
$this->assertNotNull($customer); |
||||||
|
|
||||||
|
// query scalar |
||||||
|
$customerName = Customer::find()->where(['id' => 2])->scalar('name'); |
||||||
|
$this->assertEquals('user2', $customerName); |
||||||
|
|
||||||
|
// find by column values |
||||||
|
$customer = Customer::find(['id' => 2, 'name' => 'user2']); |
||||||
|
$this->assertTrue($customer instanceof Customer); |
||||||
|
$this->assertEquals('user2', $customer->name); |
||||||
|
$customer = Customer::find(['id' => 2, 'name' => 'user1']); |
||||||
|
$this->assertNull($customer); |
||||||
|
$customer = Customer::find(['id' => 5]); |
||||||
|
$this->assertNull($customer); |
||||||
|
|
||||||
|
// find by attributes |
||||||
|
$customer = Customer::find()->where(['name' => 'user2'])->one(); |
||||||
|
$this->assertTrue($customer instanceof Customer); |
||||||
|
$this->assertEquals(2, $customer->id); |
||||||
|
|
||||||
|
// find count, sum, average, min, max, scalar |
||||||
|
$this->assertEquals(3, Customer::find()->count()); |
||||||
|
$this->assertEquals(6, Customer::find()->sum('id')); |
||||||
|
$this->assertEquals(2, Customer::find()->average('id')); |
||||||
|
$this->assertEquals(1, Customer::find()->min('id')); |
||||||
|
$this->assertEquals(3, Customer::find()->max('id')); |
||||||
|
|
||||||
|
// scope |
||||||
|
$this->assertEquals(2, Customer::find()->active()->count()); |
||||||
|
|
||||||
|
// asArray |
||||||
|
$customer = Customer::find()->where(['id' => 2])->asArray()->one(); |
||||||
|
$this->assertEquals(array( |
||||||
|
'id' => '2', |
||||||
|
'email' => 'user2@example.com', |
||||||
|
'name' => 'user2', |
||||||
|
'address' => 'address2', |
||||||
|
'status' => '1', |
||||||
|
), $customer); |
||||||
|
|
||||||
|
// indexBy |
||||||
|
$customers = Customer::find()->indexBy('name')->all(); |
||||||
|
$this->assertEquals(3, count($customers)); |
||||||
|
$this->assertTrue($customers['user1'] instanceof Customer); |
||||||
|
$this->assertTrue($customers['user2'] instanceof Customer); |
||||||
|
$this->assertTrue($customers['user3'] instanceof Customer); |
||||||
|
|
||||||
|
// indexBy callable |
||||||
|
$customers = Customer::find()->indexBy(function ($customer) { |
||||||
|
return $customer->id . '-' . $customer->name; |
||||||
|
// })->orderBy('id')->all(); |
||||||
|
})->all(); |
||||||
|
$this->assertEquals(3, count($customers)); |
||||||
|
$this->assertTrue($customers['1-user1'] instanceof Customer); |
||||||
|
$this->assertTrue($customers['2-user2'] instanceof Customer); |
||||||
|
$this->assertTrue($customers['3-user3'] instanceof Customer); |
||||||
|
} |
||||||
|
|
||||||
|
public function testFindCount() |
||||||
|
{ |
||||||
|
$this->assertEquals(3, Customer::find()->count()); |
||||||
|
$this->assertEquals(1, Customer::find()->limit(1)->count()); |
||||||
|
$this->assertEquals(2, Customer::find()->limit(2)->count()); |
||||||
|
$this->assertEquals(1, Customer::find()->offset(2)->limit(2)->count()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testFindLimit() |
||||||
|
{ |
||||||
|
// all() |
||||||
|
$customers = Customer::find()->all(); |
||||||
|
$this->assertEquals(3, count($customers)); |
||||||
|
|
||||||
|
$customers = Customer::find()->limit(1)->all(); |
||||||
|
$this->assertEquals(1, count($customers)); |
||||||
|
$this->assertEquals('user1', $customers[0]->name); |
||||||
|
|
||||||
|
$customers = Customer::find()->limit(1)->offset(1)->all(); |
||||||
|
$this->assertEquals(1, count($customers)); |
||||||
|
$this->assertEquals('user2', $customers[0]->name); |
||||||
|
|
||||||
|
$customers = Customer::find()->limit(1)->offset(2)->all(); |
||||||
|
$this->assertEquals(1, count($customers)); |
||||||
|
$this->assertEquals('user3', $customers[0]->name); |
||||||
|
|
||||||
|
$customers = Customer::find()->limit(2)->offset(1)->all(); |
||||||
|
$this->assertEquals(2, count($customers)); |
||||||
|
$this->assertEquals('user2', $customers[0]->name); |
||||||
|
$this->assertEquals('user3', $customers[1]->name); |
||||||
|
|
||||||
|
$customers = Customer::find()->limit(2)->offset(3)->all(); |
||||||
|
$this->assertEquals(0, count($customers)); |
||||||
|
|
||||||
|
// one() |
||||||
|
$customer = Customer::find()->one(); |
||||||
|
$this->assertEquals('user1', $customer->name); |
||||||
|
|
||||||
|
$customer = Customer::find()->offset(0)->one(); |
||||||
|
$this->assertEquals('user1', $customer->name); |
||||||
|
|
||||||
|
$customer = Customer::find()->offset(1)->one(); |
||||||
|
$this->assertEquals('user2', $customer->name); |
||||||
|
|
||||||
|
$customer = Customer::find()->offset(2)->one(); |
||||||
|
$this->assertEquals('user3', $customer->name); |
||||||
|
|
||||||
|
$customer = Customer::find()->offset(3)->one(); |
||||||
|
$this->assertNull($customer); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public function testFindComplexCondition() |
||||||
|
{ |
||||||
|
$this->assertEquals(2, Customer::find()->where(['OR', ['id' => 1], ['id' => 2]])->count()); |
||||||
|
$this->assertEquals(2, count(Customer::find()->where(['OR', ['id' => 1], ['id' => 2]])->all())); |
||||||
|
|
||||||
|
$this->assertEquals(2, Customer::find()->where(['id' => [1,2]])->count()); |
||||||
|
$this->assertEquals(2, count(Customer::find()->where(['id' => [1,2]])->all())); |
||||||
|
|
||||||
|
$this->assertEquals(1, Customer::find()->where(['AND', ['id' => [2,3]], ['BETWEEN', 'status', 2, 4]])->count()); |
||||||
|
$this->assertEquals(1, count(Customer::find()->where(['AND', ['id' => [2,3]], ['BETWEEN', 'status', 2, 4]])->all())); |
||||||
|
} |
||||||
|
|
||||||
|
public function testSum() |
||||||
|
{ |
||||||
|
$this->assertEquals(6, OrderItem::find()->count()); |
||||||
|
$this->assertEquals(7, OrderItem::find()->sum('quantity')); |
||||||
|
} |
||||||
|
|
||||||
|
public function testFindColumn() |
||||||
|
{ |
||||||
|
$this->assertEquals(['user1', 'user2', 'user3'], Customer::find()->column('name')); |
||||||
|
// TODO $this->assertEquals(['user3', 'user2', 'user1'], Customer::find()->orderBy(['name' => SORT_DESC])->column('name')); |
||||||
|
} |
||||||
|
|
||||||
|
public function testExists() |
||||||
|
{ |
||||||
|
$this->assertTrue(Customer::find()->where(['id' => 2])->exists()); |
||||||
|
$this->assertFalse(Customer::find()->where(['id' => 5])->exists()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testFindLazy() |
||||||
|
{ |
||||||
|
/** @var $customer Customer */ |
||||||
|
$customer = Customer::find(2); |
||||||
|
$orders = $customer->orders; |
||||||
|
$this->assertEquals(2, count($orders)); |
||||||
|
|
||||||
|
$orders = $customer->getOrders()->where(['id' => 3])->all(); |
||||||
|
$this->assertEquals(1, count($orders)); |
||||||
|
$this->assertEquals(3, $orders[0]->id); |
||||||
|
} |
||||||
|
|
||||||
|
public function testFindEager() |
||||||
|
{ |
||||||
|
$customers = Customer::find()->with('orders')->all(); |
||||||
|
$this->assertEquals(3, count($customers)); |
||||||
|
$this->assertEquals(1, count($customers[0]->orders)); |
||||||
|
$this->assertEquals(2, count($customers[1]->orders)); |
||||||
|
} |
||||||
|
|
||||||
|
public function testFindLazyVia() |
||||||
|
{ |
||||||
|
/** @var $order Order */ |
||||||
|
$order = Order::find(1); |
||||||
|
$this->assertEquals(1, $order->id); |
||||||
|
$this->assertEquals(2, count($order->items)); |
||||||
|
$this->assertEquals(1, $order->items[0]->id); |
||||||
|
$this->assertEquals(2, $order->items[1]->id); |
||||||
|
|
||||||
|
$order = Order::find(1); |
||||||
|
$order->id = 100; |
||||||
|
$this->assertEquals([], $order->items); |
||||||
|
} |
||||||
|
|
||||||
|
public function testFindEagerViaRelation() |
||||||
|
{ |
||||||
|
$orders = Order::find()->with('items')->all(); |
||||||
|
$this->assertEquals(3, count($orders)); |
||||||
|
$order = $orders[0]; |
||||||
|
$this->assertEquals(1, $order->id); |
||||||
|
$this->assertEquals(2, count($order->items)); |
||||||
|
$this->assertEquals(1, $order->items[0]->id); |
||||||
|
$this->assertEquals(2, $order->items[1]->id); |
||||||
|
} |
||||||
|
|
||||||
|
public function testFindNestedRelation() |
||||||
|
{ |
||||||
|
$customers = Customer::find()->with('orders', 'orders.items')->all(); |
||||||
|
$this->assertEquals(3, count($customers)); |
||||||
|
$this->assertEquals(1, count($customers[0]->orders)); |
||||||
|
$this->assertEquals(2, count($customers[1]->orders)); |
||||||
|
$this->assertEquals(0, count($customers[2]->orders)); |
||||||
|
$this->assertEquals(2, count($customers[0]->orders[0]->items)); |
||||||
|
$this->assertEquals(3, count($customers[1]->orders[0]->items)); |
||||||
|
$this->assertEquals(1, count($customers[1]->orders[1]->items)); |
||||||
|
} |
||||||
|
|
||||||
|
public function testLink() |
||||||
|
{ |
||||||
|
$customer = Customer::find(2); |
||||||
|
$this->assertEquals(2, count($customer->orders)); |
||||||
|
|
||||||
|
// has many |
||||||
|
$order = new Order; |
||||||
|
$order->total = 100; |
||||||
|
$this->assertTrue($order->isNewRecord); |
||||||
|
$customer->link('orders', $order); |
||||||
|
$this->assertEquals(3, count($customer->orders)); |
||||||
|
$this->assertFalse($order->isNewRecord); |
||||||
|
$this->assertEquals(3, count($customer->getOrders()->all())); |
||||||
|
$this->assertEquals(2, $order->customer_id); |
||||||
|
|
||||||
|
// belongs to |
||||||
|
$order = new Order; |
||||||
|
$order->total = 100; |
||||||
|
$this->assertTrue($order->isNewRecord); |
||||||
|
$customer = Customer::find(1); |
||||||
|
$this->assertNull($order->customer); |
||||||
|
$order->link('customer', $customer); |
||||||
|
$this->assertFalse($order->isNewRecord); |
||||||
|
$this->assertEquals(1, $order->customer_id); |
||||||
|
$this->assertEquals(1, $order->customer->id); |
||||||
|
|
||||||
|
// via model |
||||||
|
$order = Order::find(1); |
||||||
|
$this->assertEquals(2, count($order->items)); |
||||||
|
$this->assertEquals(2, count($order->orderItems)); |
||||||
|
$orderItem = OrderItem::find(['order_id' => 1, 'item_id' => 3]); |
||||||
|
$this->assertNull($orderItem); |
||||||
|
$item = Item::find(3); |
||||||
|
$order->link('items', $item, ['quantity' => 10, 'subtotal' => 100]); |
||||||
|
$this->assertEquals(3, count($order->items)); |
||||||
|
$this->assertEquals(3, count($order->orderItems)); |
||||||
|
$orderItem = OrderItem::find(['order_id' => 1, 'item_id' => 3]); |
||||||
|
$this->assertTrue($orderItem instanceof OrderItem); |
||||||
|
$this->assertEquals(10, $orderItem->quantity); |
||||||
|
$this->assertEquals(100, $orderItem->subtotal); |
||||||
|
} |
||||||
|
|
||||||
|
public function testUnlink() |
||||||
|
{ |
||||||
|
// has many |
||||||
|
$customer = Customer::find(2); |
||||||
|
$this->assertEquals(2, count($customer->orders)); |
||||||
|
$customer->unlink('orders', $customer->orders[1], true); |
||||||
|
$this->assertEquals(1, count($customer->orders)); |
||||||
|
$this->assertNull(Order::find(3)); |
||||||
|
|
||||||
|
// via model |
||||||
|
$order = Order::find(2); |
||||||
|
$this->assertEquals(3, count($order->items)); |
||||||
|
$this->assertEquals(3, count($order->orderItems)); |
||||||
|
$order->unlink('items', $order->items[2], true); |
||||||
|
$this->assertEquals(2, count($order->items)); |
||||||
|
$this->assertEquals(2, count($order->orderItems)); |
||||||
|
} |
||||||
|
|
||||||
|
public function testInsert() |
||||||
|
{ |
||||||
|
$customer = new Customer; |
||||||
|
$customer->email = 'user4@example.com'; |
||||||
|
$customer->name = 'user4'; |
||||||
|
$customer->address = 'address4'; |
||||||
|
|
||||||
|
$this->assertNull($customer->id); |
||||||
|
$this->assertTrue($customer->isNewRecord); |
||||||
|
|
||||||
|
$customer->save(); |
||||||
|
|
||||||
|
$this->assertEquals(4, $customer->id); |
||||||
|
$this->assertFalse($customer->isNewRecord); |
||||||
|
} |
||||||
|
|
||||||
|
// TODO test serial column incr |
||||||
|
|
||||||
|
public function testUpdate() |
||||||
|
{ |
||||||
|
// save |
||||||
|
$customer = Customer::find(2); |
||||||
|
$this->assertTrue($customer instanceof Customer); |
||||||
|
$this->assertEquals('user2', $customer->name); |
||||||
|
$this->assertFalse($customer->isNewRecord); |
||||||
|
$customer->name = 'user2x'; |
||||||
|
$customer->save(); |
||||||
|
$this->assertEquals('user2x', $customer->name); |
||||||
|
$this->assertFalse($customer->isNewRecord); |
||||||
|
$customer2 = Customer::find(2); |
||||||
|
$this->assertEquals('user2x', $customer2->name); |
||||||
|
|
||||||
|
// updateAll |
||||||
|
$customer = Customer::find(3); |
||||||
|
$this->assertEquals('user3', $customer->name); |
||||||
|
$ret = Customer::updateAll(array( |
||||||
|
'name' => 'temp', |
||||||
|
), ['id' => 3]); |
||||||
|
$this->assertEquals(1, $ret); |
||||||
|
$customer = Customer::find(3); |
||||||
|
$this->assertEquals('temp', $customer->name); |
||||||
|
} |
||||||
|
|
||||||
|
public function testUpdateCounters() |
||||||
|
{ |
||||||
|
// updateCounters |
||||||
|
$pk = ['order_id' => 2, 'item_id' => 4]; |
||||||
|
$orderItem = OrderItem::find($pk); |
||||||
|
$this->assertEquals(1, $orderItem->quantity); |
||||||
|
$ret = $orderItem->updateCounters(['quantity' => -1]); |
||||||
|
$this->assertTrue($ret); |
||||||
|
$this->assertEquals(0, $orderItem->quantity); |
||||||
|
$orderItem = OrderItem::find($pk); |
||||||
|
$this->assertEquals(0, $orderItem->quantity); |
||||||
|
|
||||||
|
// updateAllCounters |
||||||
|
$pk = ['order_id' => 1, 'item_id' => 2]; |
||||||
|
$orderItem = OrderItem::find($pk); |
||||||
|
$this->assertEquals(2, $orderItem->quantity); |
||||||
|
$ret = OrderItem::updateAllCounters(array( |
||||||
|
'quantity' => 3, |
||||||
|
'subtotal' => -10, |
||||||
|
), $pk); |
||||||
|
$this->assertEquals(1, $ret); |
||||||
|
$orderItem = OrderItem::find($pk); |
||||||
|
$this->assertEquals(5, $orderItem->quantity); |
||||||
|
$this->assertEquals(30, $orderItem->subtotal); |
||||||
|
} |
||||||
|
|
||||||
|
public function testUpdatePk() |
||||||
|
{ |
||||||
|
// updateCounters |
||||||
|
$pk = ['order_id' => 2, 'item_id' => 4]; |
||||||
|
$orderItem = OrderItem::find($pk); |
||||||
|
$this->assertEquals(2, $orderItem->order_id); |
||||||
|
$this->assertEquals(4, $orderItem->item_id); |
||||||
|
|
||||||
|
$orderItem->order_id = 2; |
||||||
|
$orderItem->item_id = 10; |
||||||
|
$orderItem->save(); |
||||||
|
|
||||||
|
$this->assertNull(OrderItem::find($pk)); |
||||||
|
$this->assertNotNull(OrderItem::find(['order_id' => 2, 'item_id' => 10])); |
||||||
|
} |
||||||
|
|
||||||
|
public function testDelete() |
||||||
|
{ |
||||||
|
// delete |
||||||
|
$customer = Customer::find(2); |
||||||
|
$this->assertTrue($customer instanceof Customer); |
||||||
|
$this->assertEquals('user2', $customer->name); |
||||||
|
$customer->delete(); |
||||||
|
$customer = Customer::find(2); |
||||||
|
$this->assertNull($customer); |
||||||
|
|
||||||
|
// deleteAll |
||||||
|
$customers = Customer::find()->all(); |
||||||
|
$this->assertEquals(2, count($customers)); |
||||||
|
$ret = Customer::deleteAll(); |
||||||
|
$this->assertEquals(2, $ret); |
||||||
|
$customers = Customer::find()->all(); |
||||||
|
$this->assertEquals(0, count($customers)); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,69 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\framework\redis; |
||||||
|
|
||||||
|
use yii\redis\Connection; |
||||||
|
|
||||||
|
/** |
||||||
|
* @group redis |
||||||
|
*/ |
||||||
|
class RedisConnectionTest extends RedisTestCase |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Empty DSN should throw exception |
||||||
|
* @expectedException \yii\base\InvalidConfigException |
||||||
|
*/ |
||||||
|
public function testEmptyDSN() |
||||||
|
{ |
||||||
|
$db = new Connection(); |
||||||
|
$db->open(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* test connection to redis and selection of db |
||||||
|
*/ |
||||||
|
public function testConnect() |
||||||
|
{ |
||||||
|
$db = new Connection(); |
||||||
|
$db->dsn = 'redis://localhost:6379'; |
||||||
|
$db->open(); |
||||||
|
$this->assertTrue($db->ping()); |
||||||
|
$db->set('YIITESTKEY', 'YIITESTVALUE'); |
||||||
|
$db->close(); |
||||||
|
|
||||||
|
$db = new Connection(); |
||||||
|
$db->dsn = 'redis://localhost:6379/0'; |
||||||
|
$db->open(); |
||||||
|
$this->assertEquals('YIITESTVALUE', $db->get('YIITESTKEY')); |
||||||
|
$db->close(); |
||||||
|
|
||||||
|
$db = new Connection(); |
||||||
|
$db->dsn = 'redis://localhost:6379/1'; |
||||||
|
$db->open(); |
||||||
|
$this->assertNull($db->get('YIITESTKEY')); |
||||||
|
$db->close(); |
||||||
|
} |
||||||
|
|
||||||
|
public function keyValueData() |
||||||
|
{ |
||||||
|
return array( |
||||||
|
array(123), |
||||||
|
array(-123), |
||||||
|
array(0), |
||||||
|
array('test'), |
||||||
|
array("test\r\ntest"), |
||||||
|
array(''), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @dataProvider keyValueData |
||||||
|
*/ |
||||||
|
public function testStoreGet($data) |
||||||
|
{ |
||||||
|
$db = $this->getConnection(true); |
||||||
|
|
||||||
|
$db->set('hi', $data); |
||||||
|
$this->assertEquals($data, $db->get('hi')); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,51 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\framework\redis; |
||||||
|
|
||||||
|
use yii\redis\Connection; |
||||||
|
use yiiunit\TestCase; |
||||||
|
|
||||||
|
/** |
||||||
|
* RedisTestCase is the base class for all redis related test cases |
||||||
|
*/ |
||||||
|
abstract class RedisTestCase extends TestCase |
||||||
|
{ |
||||||
|
protected function setUp() |
||||||
|
{ |
||||||
|
$this->mockApplication(); |
||||||
|
|
||||||
|
$databases = $this->getParam('databases'); |
||||||
|
$params = isset($databases['redis']) ? $databases['redis'] : null; |
||||||
|
if ($params === null || !isset($params['dsn'])) { |
||||||
|
$this->markTestSkipped('No redis server connection configured.'); |
||||||
|
} |
||||||
|
$dsn = explode('/', $params['dsn']); |
||||||
|
$host = $dsn[2]; |
||||||
|
if (strpos($host, ':')===false) { |
||||||
|
$host .= ':6379'; |
||||||
|
} |
||||||
|
if(!@stream_socket_client($host, $errorNumber, $errorDescription, 0.5)) { |
||||||
|
$this->markTestSkipped('No redis server running at ' . $params['dsn'] . ' : ' . $errorNumber . ' - ' . $errorDescription); |
||||||
|
} |
||||||
|
|
||||||
|
parent::setUp(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param bool $reset whether to clean up the test database |
||||||
|
* @return Connection |
||||||
|
*/ |
||||||
|
public function getConnection($reset = true) |
||||||
|
{ |
||||||
|
$databases = $this->getParam('databases'); |
||||||
|
$params = isset($databases['redis']) ? $databases['redis'] : array(); |
||||||
|
$db = new Connection; |
||||||
|
$db->dsn = $params['dsn']; |
||||||
|
$db->password = $params['password']; |
||||||
|
if ($reset) { |
||||||
|
$db->open(); |
||||||
|
$db->flushall(); |
||||||
|
} |
||||||
|
return $db; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue