Browse Source

Redis Insert, Update, Delete and Find is ready and roughly unit tested

relations are not working yet
tags/2.0.0-beta
Carsten Brandt 12 years ago
parent
commit
79982c9848
  1. 32
      framework/db/redis/ActiveQuery.php
  2. 294
      framework/db/redis/ActiveRecord.php
  3. 230
      framework/db/redis/ActiveRelation.php
  4. 2
      framework/db/redis/Connection.php
  5. 26
      tests/unit/data/ar/redis/ActiveRecord.php
  6. 35
      tests/unit/data/ar/redis/Customer.php
  7. 25
      tests/unit/data/ar/redis/Item.php
  8. 63
      tests/unit/data/ar/redis/Order.php
  9. 36
      tests/unit/data/ar/redis/OrderItem.php
  10. 402
      tests/unit/framework/db/redis/ActiveRecordTest.php

32
framework/db/redis/ActiveQuery.php

@ -80,8 +80,19 @@ class ActiveQuery extends \yii\base\Component
*/ */
public $primaryKeys; public $primaryKeys;
/**
* List of multiple pks must be zero based
*
* @param $primaryKeys
* @return ActiveQuery
*/
public function primaryKeys($primaryKeys) { public function primaryKeys($primaryKeys) {
if (is_array($primaryKeys) && isset($primaryKeys[0])) {
$this->primaryKeys = $primaryKeys; $this->primaryKeys = $primaryKeys;
} else {
$this->primaryKeys = array($primaryKeys);
}
return $this; return $this;
} }
@ -103,7 +114,12 @@ class ActiveQuery extends \yii\base\Component
foreach($primaryKeys as $pk) { foreach($primaryKeys as $pk) {
$key = $modelClass::tableName() . ':a:' . (is_array($pk) ? implode('-', $pk) : $pk); // TODO escape PK glue $key = $modelClass::tableName() . ':a:' . (is_array($pk) ? implode('-', $pk) : $pk); // TODO escape PK glue
// get attributes // get attributes
$rows[] = $db->executeCommand('HGETALL', array($key)); $data = $db->executeCommand('HGETALL', array($key));
$row = array();
for($i=0;$i<count($data);) {
$row[$data[$i++]] = $data[$i++];
}
$rows[] = $row;
} }
if ($rows !== array()) { if ($rows !== array()) {
$models = $this->createModels($rows); $models = $this->createModels($rows);
@ -134,9 +150,15 @@ class ActiveQuery extends \yii\base\Component
$pk = reset($primaryKeys); $pk = reset($primaryKeys);
$key = $modelClass::tableName() . ':a:' . (is_array($pk) ? implode('-', $pk) : $pk); // TODO escape PK glue $key = $modelClass::tableName() . ':a:' . (is_array($pk) ? implode('-', $pk) : $pk); // TODO escape PK glue
// get attributes // get attributes
$row = $db->executeCommand('HGETALL', array($key)); $data = $db->executeCommand('HGETALL', array($key));
// TODO check for empty list if key does not exist if ($data === array()) {
if ($row !== false && !$this->asArray) { return null;
}
$row = array();
for($i=0;$i<count($data);) {
$row[$data[$i++]] = $data[$i++];
}
if (!$this->asArray) {
/** @var $class ActiveRecord */ /** @var $class ActiveRecord */
$class = $this->modelClass; $class = $this->modelClass;
$model = $class::create($row); $model = $class::create($row);
@ -147,7 +169,7 @@ class ActiveQuery extends \yii\base\Component
} }
return $model; return $model;
} else { } else {
return $row === false ? null : $row; return $row;
} }
} }

294
framework/db/redis/ActiveRecord.php

@ -10,7 +10,12 @@
namespace yii\db\redis; namespace yii\db\redis;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
use yii\base\NotSupportedException; use yii\base\NotSupportedException;
use yii\base\UnknownMethodException;
use yii\db\Exception;
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.
@ -23,16 +28,6 @@ use yii\base\NotSupportedException;
abstract class ActiveRecord extends \yii\db\ActiveRecord abstract class ActiveRecord extends \yii\db\ActiveRecord
{ {
/** /**
* Returns the list of all attribute names of the model.
* The default implementation will return all column names of the table associated with this AR class.
* @return array list of attribute names.
*/
public function attributes() // TODO: refactor should be abstract in an ActiveRecord base class
{
return array();
}
/**
* Returns the database connection used by this AR class. * Returns the database connection used by this AR class.
* By default, the "redis" application component is used as the database connection. * 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. * You may override this method if you want to use a different database connection.
@ -119,7 +114,7 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord
*/ */
public static function getTableSchema() public static function getTableSchema()
{ {
throw new NotSupportedException('getTableSchema() is not supported by redis ActiveRecord as there is no schema in redis DB. Schema is defined by AR class itself'); throw new InvalidConfigException(__CLASS__.'::getTableSchema() needs to be overridden in subclasses and return a TableSchema.');
} }
/** /**
@ -168,16 +163,17 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord
$db = static::getDb(); $db = static::getDb();
$values = $this->getDirtyAttributes($attributes); $values = $this->getDirtyAttributes($attributes);
$pk = array(); $pk = array();
if ($values === array()) { // if ($values === array()) {
foreach ($this->primaryKey() as $key) { foreach ($this->primaryKey() as $key) {
$pk[$key] = $values[$key] = $this->getAttribute($key); $pk[$key] = $values[$key] = $this->getAttribute($key);
if ($pk[$key] === null) { if ($pk[$key] === null) {
$pk[$key] = $db->executeCommand('INCR', array(static::tableName() . ':s:', $key)); $pk[$key] = $values[$key] = $db->executeCommand('INCR', array(static::tableName() . ':s:' . $key));
} $this->setAttribute($key, $values[$key]);
} }
} }
// }
// save pk in a findall pool // save pk in a findall pool
$db->executeCommand('RPUSH', array(static::tableName(), $pk)); $db->executeCommand('RPUSH', array(static::tableName(), implode('-', $pk))); // TODO escape PK glue
$key = static::tableName() . ':a:' . implode('-', $pk); // TODO escape PK glue $key = static::tableName() . ':a:' . implode('-', $pk); // TODO escape PK glue
// save attributes // save attributes
@ -252,12 +248,15 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord
*/ */
public static function updateAllCounters($counters, $condition = '', $params = array()) public static function updateAllCounters($counters, $condition = '', $params = array())
{ {
if (is_array($condition) && !isset($condition[0])) { // TODO do this in all *All methods
$condition = array($condition);
}
$db = static::getDb(); $db = static::getDb();
if ($condition==='') { if ($condition==='') {
$condition = $db->executeCommand('LRANGE', array(static::tableName(), 0, -1)); $condition = $db->executeCommand('LRANGE', array(static::tableName(), 0, -1));
} }
$n=0; $n=0;
foreach($condition as $pk) { foreach($condition as $pk) { // TODO allow multiple pks as condition
$key = static::tableName() . ':a:' . (is_array($pk) ? implode('-', $pk) : $pk); // TODO escape PK glue $key = static::tableName() . ':a:' . (is_array($pk) ? implode('-', $pk) : $pk); // TODO escape PK glue
foreach($counters as $attribute => $value) { foreach($counters as $attribute => $value) {
$db->executeCommand('HINCRBY', array($key, $attribute, $value)); $db->executeCommand('HINCRBY', array($key, $attribute, $value));
@ -297,28 +296,269 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord
$pk = implode('-', $pk); $pk = implode('-', $pk);
} }
$db->executeCommand('LREM', array(static::tableName(), 0, $pk)); // TODO escape PK glue $db->executeCommand('LREM', array(static::tableName(), 0, $pk)); // TODO escape PK glue
$attributeKeys[] = static::tableName() . ':' . $pk . ':a'; // TODO escape PK glue $attributeKeys[] = static::tableName() . ':a:' . $pk; // TODO escape PK glue
} }
return $db->executeCommand('DEL', $attributeKeys); return $db->executeCommand('DEL', $attributeKeys);
} }
/** /**
* Returns the primary key name(s) for this AR class. * Declares a `has-one` relation.
* The default implementation will return the primary key(s) as declared * The declaration is returned in terms of an [[ActiveRelation]] instance
* in the DB table that is associated with this AR class. * through which the related record can be queried and retrieved back.
*
* A `has-one` relation means that there is at most one related record matching
* the criteria set by this relation, e.g., a customer has one country.
*
* For example, to declare the `country` relation for `Customer` class, we can write
* the following code in the `Customer` class:
*
* ~~~
* public function getCountry()
* {
* return $this->hasOne('Country', array('id' => 'country_id'));
* }
* ~~~
*
* Note that in the above, the 'id' key in the `$link` parameter refers to an attribute name
* in the related class `Country`, while the 'country_id' value refers to an attribute name
* in the current AR class.
*
* Call methods declared in [[ActiveRelation]] to further customize the relation.
*
* @param string $class the class name of the related record
* @param array $link the primary-foreign key constraint. The keys of the array refer to
* the columns in the table associated with the `$class` model, while the values of the
* array refer to the corresponding columns in the table associated with this AR class.
* @return ActiveRelation the relation object.
*/
public function hasOne($class, $link)
{
return new ActiveRelation(array(
'modelClass' => $this->getNamespacedClass($class),
'primaryModel' => $this,
'link' => $link,
'multiple' => false,
));
}
/**
* Declares a `has-many` relation.
* The declaration is returned in terms of an [[ActiveRelation]] instance
* through which the related record can be queried and retrieved back.
*
* A `has-many` relation means that there are multiple related records matching
* the criteria set by this relation, e.g., a customer has many orders.
*
* For example, to declare the `orders` relation for `Customer` class, we can write
* the following code in the `Customer` class:
*
* ~~~
* public function getOrders()
* {
* return $this->hasMany('Order', array('customer_id' => 'id'));
* }
* ~~~
*
* Note that in the above, the 'customer_id' key in the `$link` parameter refers to
* an attribute name in the related class `Order`, while the 'id' value refers to
* an attribute name in the current AR class.
*
* @param string $class the class name of the related record
* @param array $link the primary-foreign key constraint. The keys of the array refer to
* the columns in the table associated with the `$class` model, while the values of the
* array refer to the corresponding columns in the table associated with this AR class.
* @return ActiveRelation the relation object.
*/
public function hasMany($class, $link)
{
return new ActiveRelation(array(
'modelClass' => $this->getNamespacedClass($class),
'primaryModel' => $this,
'link' => $link,
'multiple' => true,
));
}
/**
* Returns the relation object with the specified name.
* A relation is defined by a getter method which returns an [[ActiveRelation]] object.
* It can be declared in either the Active Record class itself or one of its behaviors.
* @param string $name the relation name
* @return ActiveRelation the relation object
* @throws InvalidParamException if the named relation does not exist.
*/
public function getRelation($name)
{
$getter = 'get' . $name;
try {
$relation = $this->$getter();
if ($relation instanceof ActiveRelation) {
return $relation;
}
} catch (UnknownMethodException $e) {
}
throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".');
}
/**
* Establishes the relationship between two models.
*
* The relationship is established by setting the foreign key value(s) in one model
* to be the corresponding primary key value(s) in the other model.
* The model with the foreign key will be saved into database without performing validation.
* *
* If the DB table does not declare any primary key, you should override * If the relationship involves a pivot table, a new row will be inserted into the
* this method to return the attributes that you want to use as primary keys * pivot table which contains the primary key values from both models.
* for this AR class. *
* Note that this method requires that the primary key value is not null.
*
* @param string $name the name of the relationship
* @param ActiveRecord $model the model to be linked with the current one.
* @param array $extraColumns additional column values to be saved into the pivot table.
* This parameter is only meaningful for a relationship involving a pivot table
* (i.e., a relation set with `[[ActiveRelation::via()]]` or `[[ActiveRelation::viaTable()]]`.)
* @throws InvalidCallException if the method is unable to link two models.
*/
public function link($name, $model, $extraColumns = array())
{
// TODO
$relation = $this->getRelation($name);
if ($relation->via !== null) {
if (is_array($relation->via)) {
/** @var $viaRelation ActiveRelation */
list($viaName, $viaRelation) = $relation->via;
/** @var $viaClass ActiveRecord */
$viaClass = $viaRelation->modelClass;
$viaTable = $viaClass::tableName();
// unset $viaName so that it can be reloaded to reflect the change
unset($this->_related[strtolower($viaName)]);
} else {
$viaRelation = $relation->via;
$viaTable = reset($relation->via->from);
}
$columns = array();
foreach ($viaRelation->link as $a => $b) {
$columns[$a] = $this->$b;
}
foreach ($relation->link as $a => $b) {
$columns[$b] = $model->$a;
}
foreach ($extraColumns as $k => $v) {
$columns[$k] = $v;
}
static::getDb()->createCommand()
->insert($viaTable, $columns)->execute();
} else {
$p1 = $model->isPrimaryKey(array_keys($relation->link));
$p2 = $this->isPrimaryKey(array_values($relation->link));
if ($p1 && $p2) {
if ($this->getIsNewRecord() && $model->getIsNewRecord()) {
throw new InvalidCallException('Unable to link models: both models are newly created.');
} elseif ($this->getIsNewRecord()) {
$this->bindModels(array_flip($relation->link), $this, $model);
} else {
$this->bindModels($relation->link, $model, $this);
}
} elseif ($p1) {
$this->bindModels(array_flip($relation->link), $this, $model);
} elseif ($p2) {
$this->bindModels($relation->link, $model, $this);
} else {
throw new InvalidCallException('Unable to link models: the link does not involve any primary key.');
}
}
// update lazily loaded related objects
if (!$relation->multiple) {
$this->_related[$name] = $model;
} elseif (isset($this->_related[$name])) {
if ($relation->indexBy !== null) {
$indexBy = $relation->indexBy;
$this->_related[$name][$model->$indexBy] = $model;
} else {
$this->_related[$name][] = $model;
}
}
}
/**
* Destroys the relationship between two models.
* *
* Note that an array should be returned even for a table with single primary key. * The model with the foreign key of the relationship will be deleted if `$delete` is true.
* Otherwise, the foreign key will be set null and the model will be saved without validation.
* *
* @return string[] the primary keys of the associated database table. * @param string $name the name of the relationship.
* @param ActiveRecord $model the model to be unlinked from the current one.
* @param boolean $delete whether to delete the model that contains the foreign key.
* If false, the model's foreign key will be set null and saved.
* If true, the model containing the foreign key will be deleted.
* @throws InvalidCallException if the models cannot be unlinked
*/ */
public static function primaryKey() // TODO: refactor should be abstract in an ActiveRecord base class public function unlink($name, $model, $delete = false)
{ {
return array(); // TODO
$relation = $this->getRelation($name);
if ($relation->via !== null) {
if (is_array($relation->via)) {
/** @var $viaRelation ActiveRelation */
list($viaName, $viaRelation) = $relation->via;
/** @var $viaClass ActiveRecord */
$viaClass = $viaRelation->modelClass;
$viaTable = $viaClass::tableName();
unset($this->_related[strtolower($viaName)]);
} else {
$viaRelation = $relation->via;
$viaTable = reset($relation->via->from);
}
$columns = array();
foreach ($viaRelation->link as $a => $b) {
$columns[$a] = $this->$b;
}
foreach ($relation->link as $a => $b) {
$columns[$b] = $model->$a;
}
$command = static::getDb()->createCommand();
if ($delete) {
$command->delete($viaTable, $columns)->execute();
} else {
$nulls = array();
foreach (array_keys($columns) as $a) {
$nulls[$a] = null;
}
$command->update($viaTable, $nulls, $columns)->execute();
}
} else {
$p1 = $model->isPrimaryKey(array_keys($relation->link));
$p2 = $this->isPrimaryKey(array_values($relation->link));
if ($p1 && $p2 || $p2) {
foreach ($relation->link as $a => $b) {
$model->$a = null;
} }
$delete ? $model->delete() : $model->save(false);
} elseif ($p1) {
foreach ($relation->link as $b) {
$this->$b = null;
}
$delete ? $this->delete() : $this->save(false);
} else {
throw new InvalidCallException('Unable to unlink models: the link does not involve any primary key.');
}
}
if (!$relation->multiple) {
unset($this->_related[$name]);
} elseif (isset($this->_related[$name])) {
/** @var $b ActiveRecord */
foreach ($this->_related[$name] as $a => $b) {
if ($model->getPrimaryKey() == $b->getPrimaryKey()) {
unset($this->_related[$name][$a]);
}
}
}
}
// TODO implement link and unlink // TODO implement link and unlink
} }

230
framework/db/redis/ActiveRelation.php

@ -10,6 +10,8 @@
namespace yii\db\redis; namespace yii\db\redis;
use yii\base\NotSupportedException;
/** /**
* 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.
* *
@ -17,7 +19,231 @@ namespace yii\db\redis;
* @author Carsten Brandt <mail@cebe.cc> * @author Carsten Brandt <mail@cebe.cc>
* @since 2.0 * @since 2.0
*/ */
class ActiveRelation extends \yii\db\ActiveRelation class ActiveRelation extends \yii\db\redis\ActiveQuery
{
/**
* @var boolean whether this relation should populate all query results into AR instances.
* If false, only the first row of the results will be retrieved.
*/
public $multiple;
/**
* @var ActiveRecord the primary model that this relation is associated with.
* This is used only in lazy loading with dynamic query options.
*/
public $primaryModel;
/**
* @var array the columns of the primary and foreign tables that establish the relation.
* The array keys must be columns of the table for this relation, and the array values
* must be the corresponding columns from the primary table.
* Do not prefix or quote the column names as this will be done automatically by Yii.
*/
public $link;
/**
* @var array|ActiveRelation the query associated with the pivot table. Please call [[via()]]
* or [[viaTable()]] to set this property instead of directly setting it.
*/
public $via;
/**
* Specifies the relation associated with the pivot table.
* @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]].
* @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
* Its signature should be `function($query)`, where `$query` is the query to be customized.
* @return ActiveRelation the relation object itself.
*/
public function via($relationName, $callable = null)
{
$relation = $this->primaryModel->getRelation($relationName);
$this->via = array($relationName, $relation);
if ($callable !== null) {
call_user_func($callable, $relation);
}
return $this;
}
/**
* Specifies the pivot table.
* @param string $tableName the name of the pivot table.
* @param array $link the link between the pivot table and the table associated with [[primaryModel]].
* The keys of the array represent the columns in the pivot table, and the values represent the columns
* in the [[primaryModel]] table.
* @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
* Its signature should be `function($query)`, where `$query` is the query to be customized.
* @return ActiveRelation
* /
public function viaTable($tableName, $link, $callable = null)
{
$relation = new ActiveRelation(array(
'modelClass' => get_class($this->primaryModel),
'from' => array($tableName),
'link' => $link,
'multiple' => true,
'asArray' => true,
));
$this->via = $relation;
if ($callable !== null) {
call_user_func($callable, $relation);
}
return $this;
}*/
/**
* Finds the related records and populates them into the primary models.
* This method is internally by [[ActiveQuery]]. Do not call it directly.
* @param string $name the relation name
* @param array $primaryModels primary models
* @return array the related models
* @throws InvalidConfigException
*/
public function findWith($name, &$primaryModels)
{
if (!is_array($this->link)) {
throw new InvalidConfigException('Invalid link: it must be an array of key-value pairs.');
}
if ($this->via instanceof self) {
// TODO
// via pivot table
/** @var $viaQuery ActiveRelation */
$viaQuery = $this->via;
$viaModels = $viaQuery->findPivotRows($primaryModels);
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// TODO
// via relation
/** @var $viaQuery ActiveRelation */
list($viaName, $viaQuery) = $this->via;
$viaQuery->primaryModel = null;
$viaModels = $viaQuery->findWith($viaName, $primaryModels);
$this->filterByModels($viaModels);
} else {
$this->filterByModels($primaryModels);
}
if (count($primaryModels) === 1 && !$this->multiple) {
$model = $this->one();
foreach ($primaryModels as $i => $primaryModel) {
if ($primaryModel instanceof ActiveRecord) {
$primaryModel->populateRelation($name, $model);
} else {
$primaryModels[$i][$name] = $model;
}
}
return array($model);
} else {
$models = $this->all();
if (isset($viaModels, $viaQuery)) {
$buckets = $this->buildBuckets($models, $this->link, $viaModels, $viaQuery->link);
} else {
$buckets = $this->buildBuckets($models, $this->link);
}
$link = array_values(isset($viaQuery) ? $viaQuery->link : $this->link);
foreach ($primaryModels as $i => $primaryModel) {
$key = $this->getModelKey($primaryModel, $link);
$value = isset($buckets[$key]) ? $buckets[$key] : ($this->multiple ? array() : null);
if ($primaryModel instanceof ActiveRecord) {
$primaryModel->populateRelation($name, $value);
} else {
$primaryModels[$i][$name] = $value;
}
}
return $models;
}
}
/**
* @param array $models
* @param array $link
* @param array $viaModels
* @param array $viaLink
* @return array
*/
private function buildBuckets($models, $link, $viaModels = null, $viaLink = null)
{ {
// TODO implement $buckets = array();
$linkKeys = array_keys($link);
foreach ($models as $i => $model) {
$key = $this->getModelKey($model, $linkKeys);
if ($this->indexBy !== null) {
$buckets[$key][$i] = $model;
} else {
$buckets[$key][] = $model;
}
}
if ($viaModels !== null) {
$viaBuckets = array();
$viaLinkKeys = array_keys($viaLink);
$linkValues = array_values($link);
foreach ($viaModels as $viaModel) {
$key1 = $this->getModelKey($viaModel, $viaLinkKeys);
$key2 = $this->getModelKey($viaModel, $linkValues);
if (isset($buckets[$key2])) {
foreach ($buckets[$key2] as $i => $bucket) {
if ($this->indexBy !== null) {
$viaBuckets[$key1][$i] = $bucket;
} else {
$viaBuckets[$key1][] = $bucket;
}
}
}
}
$buckets = $viaBuckets;
}
if (!$this->multiple) {
foreach ($buckets as $i => $bucket) {
$buckets[$i] = reset($bucket);
}
}
return $buckets;
}
/**
* @param ActiveRecord|array $model
* @param array $attributes
* @return string
*/
private function getModelKey($model, $attributes)
{
if (count($attributes) > 1) {
$key = array();
foreach ($attributes as $attribute) {
$key[] = $model[$attribute];
}
return serialize($key);
} else {
$attribute = reset($attributes);
return $model[$attribute];
}
}
/**
* @param array $models
*/
private function filterByModels($models)
{
$attributes = array_keys($this->link);
$values = array();
if (count($attributes) ===1) {
// single key
$attribute = reset($this->link);
foreach ($models as $model) {
$values[] = $model[$attribute];
}
} else {
// composite keys
foreach ($models as $model) {
$v = array();
foreach ($this->link as $attribute => $link) {
$v[$attribute] = $model[$link];
}
$values[] = $v;
}
}
$this->primaryKeys($values);
}
} }

2
framework/db/redis/Connection.php

@ -12,7 +12,7 @@ namespace yii\db\redis;
use \yii\base\Component; use \yii\base\Component;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use \yii\db\Exception; use \yii\db\Exception;
use yii\util\StringHelper; use yii\helpers\StringHelper;
/** /**
* *

26
tests/unit/data/ar/redis/ActiveRecord.php

@ -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\db\redis\Connection;
/**
* ActiveRecord is ...
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ActiveRecord extends \yii\db\redis\ActiveRecord
{
public static $db;
public static function getDb()
{
return self::$db;
}
}

35
tests/unit/data/ar/redis/Customer.php

@ -0,0 +1,35 @@
<?php
namespace yiiunit\data\ar\redis;
use yii\db\TableSchema;
class Customer extends ActiveRecord
{
const STATUS_ACTIVE = 1;
const STATUS_INACTIVE = 2;
public $status2;
/**
* @return \yii\db\redis\ActiveRelation
*/
public function getOrders()
{
return $this->hasMany('Order', array('customer_id' => 'id'));
}
public static function getTableSchema()
{
return new TableSchema(array(
'primaryKey' => array('id'),
'columns' => array(
'id' => 'integer',
'email' => 'string',
'name' => 'string',
'address' => 'string',
'status' => 'integer'
)
));
}
}

25
tests/unit/data/ar/redis/Item.php

@ -0,0 +1,25 @@
<?php
namespace yiiunit\data\ar\redis;
use yii\db\TableSchema;
class Item extends ActiveRecord
{
public static function tableName()
{
return 'tbl_item';
}
public static function getTableSchema()
{
return new TableSchema(array(
'primaryKey' => array('id'),
'columns' => array(
'id' => 'integer',
'name' => 'string',
'category_id' => 'integer'
)
));
}
}

63
tests/unit/data/ar/redis/Order.php

@ -0,0 +1,63 @@
<?php
namespace yiiunit\data\ar\redis;
use yii\db\TableSchema;
class Order extends ActiveRecord
{
public static function tableName()
{
return 'tbl_order';
}
public function getCustomer()
{
return $this->hasOne('Customer', array('id' => 'customer_id'));
}
public function getOrderItems()
{
return $this->hasMany('OrderItem', array('order_id' => 'id'));
}
public function getItems()
{
return $this->hasMany('Item', array('id' => 'item_id'))
->via('orderItems', function($q) {
// additional query configuration
});
}
public function getBooks()
{
return $this->hasMany('Item', array('id' => 'item_id'))
->via('orderItems', array('order_id' => 'id'));
//->where(array('category_id' => 1));
}
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
$this->create_time = time();
return true;
} else {
return false;
}
}
public static function getTableSchema()
{
return new TableSchema(array(
'primaryKey' => array('id'),
'columns' => array(
'id' => 'integer',
'customer_id' => 'integer',
'create_time' => 'integer',
'total' => 'decimal',
)
));
}
}

36
tests/unit/data/ar/redis/OrderItem.php

@ -0,0 +1,36 @@
<?php
namespace yiiunit\data\ar\redis;
use yii\db\TableSchema;
class OrderItem extends ActiveRecord
{
public static function tableName()
{
return 'tbl_order_item';
}
public function getOrder()
{
return $this->hasOne('Order', array('id' => 'order_id'));
}
public function getItem()
{
return $this->hasOne('Item', array('id' => 'item_id'));
}
public static function getTableSchema()
{
return new TableSchema(array(
'primaryKey' => array('order_id', 'item_id'),
'columns' => array(
'order_id' => 'integer',
'item_id' => 'integer',
'quantity' => 'integer',
'subtotal' => 'decimal',
)
));
}
}

402
tests/unit/framework/db/redis/ActiveRecordTest.php

@ -0,0 +1,402 @@
<?php
namespace yiiunit\framework\db\redis;
use yii\base\Exception;
use yii\db\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;
class ActiveRecordTest extends RedisTestCase
{
public function setUp()
{
ActiveRecord::$db = $this->getConnection();
$customer = new Customer();
$customer->setAttributes(array('email' => 'user1@example.com', 'name' => 'user1', 'address' => 'address1', 'status' => 1), false);
$customer->save(false);
$customer = new Customer();
$customer->setAttributes(array('email' => 'user2@example.com', 'name' => 'user2', 'address' => 'address2', 'status' => 1), false);
$customer->save(false);
$customer = new Customer();
$customer->setAttributes(array('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(array('name' => 'Agile Web Application Development with Yii1.1 and PHP5', 'category_id' => 1), false);
$item->save(false);
$item = new Item();
$item->setAttributes(array('name' => 'Yii 1.1 Application Development Cookbook', 'category_id' => 1), false);
$item->save(false);
$item = new Item();
$item->setAttributes(array('name' => 'Ice Age', 'category_id' => 2), false);
$item->save(false);
$item = new Item();
$item->setAttributes(array('name' => 'Toy Story', 'category_id' => 2), false);
$item->save(false);
$item = new Item();
$item->setAttributes(array('name' => 'Cars', 'category_id' => 2), false);
$item->save(false);
$order = new Order();
$order->setAttributes(array('customer_id' => 1, 'create_time' => 1325282384, 'total' => 110.0), false);
$order->save(false);
$order = new Order();
$order->setAttributes(array('customer_id' => 2, 'create_time' => 1325334482, 'total' => 33.0), false);
$order->save(false);
$order = new Order();
$order->setAttributes(array('customer_id' => 2, 'create_time' => 1325502201, 'total' => 40.0), false);
$order->save(false);
$orderItem = new OrderItem();
$orderItem->setAttributes(array('order_id' => 1, 'item_id' => 1, 'quantity' => 1, 'subtotal' => 30.0), false);
$orderItem->save(false);
$orderItem = new OrderItem();
$orderItem->setAttributes(array('order_id' => 1, 'item_id' => 2, 'quantity' => 2, 'subtotal' => 40.0), false);
$orderItem->save(false);
$orderItem = new OrderItem();
$orderItem->setAttributes(array('order_id' => 2, 'item_id' => 4, 'quantity' => 1, 'subtotal' => 10.0), false);
$orderItem->save(false);
$orderItem = new OrderItem();
$orderItem->setAttributes(array('order_id' => 2, 'item_id' => 5, 'quantity' => 1, 'subtotal' => 15.0), false);
$orderItem->save(false);
$orderItem = new OrderItem();
$orderItem->setAttributes(array('order_id' => 2, 'item_id' => 3, 'quantity' => 1, 'subtotal' => 8.0), false);
$orderItem->save(false);
$orderItem = new OrderItem();
$orderItem->setAttributes(array('order_id' => 3, 'item_id' => 2, 'quantity' => 1, 'subtotal' => 40.0), false);
$orderItem->save(false);
parent::setUp();
}
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);
// find by column values
$customer = Customer::find(array('id' => 2));
$this->assertTrue($customer instanceof Customer);
$this->assertEquals('user2', $customer->name);
$customer = Customer::find(array('id' => 5));
$this->assertNull($customer);
// find by attributes
/* $customer = Customer::find()->where(array('name' => 'user2'))->one();
$this->assertTrue($customer instanceof Customer);
$this->assertEquals(2, $customer->id);*/
// find custom column
/* $customer = Customer::find()->select(array('*', '(status*2) AS status2'))
->where(array('name' => 'user3'))->one();
$this->assertEquals(3, $customer->id);
$this->assertEquals(4, $customer->status2);*/
// find count, sum, average, min, max, scalar
$this->assertEquals(3, Customer::find()->count());
/* $this->assertEquals(2, Customer::find()->where('id=1 OR id=2')->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'));
$this->assertEquals(3, Customer::find()->select('COUNT(*)')->scalar());*/
// scope
// $this->assertEquals(2, Customer::find()->active()->count());
// asArray
$customer = Customer::find()->primaryKeys(array(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);
}
public function testFindLazy()
{
/** @var $customer Customer */
$customer = Customer::find(2);
$orders = $customer->orders;
$this->assertEquals(2, count($orders));
$orders = $customer->getOrders()->primaryKeys(array(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(array(), $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 testFindLazyViaTable()
{
/** @var $order Order * /
$order = Order::find(1);
$this->assertEquals(1, $order->id);
$this->assertEquals(2, count($order->books));
$this->assertEquals(1, $order->items[0]->id);
$this->assertEquals(2, $order->items[1]->id);
$order = Order::find(2);
$this->assertEquals(2, $order->id);
$this->assertEquals(0, count($order->books));
}
public function testFindEagerViaTable()
{
$orders = Order::find()->with('books')->all();
$this->assertEquals(3, count($orders));
$order = $orders[0];
$this->assertEquals(1, $order->id);
$this->assertEquals(2, count($order->books));
$this->assertEquals(1, $order->books[0]->id);
$this->assertEquals(2, $order->books[1]->id);
$order = $orders[1];
$this->assertEquals(2, $order->id);
$this->assertEquals(0, count($order->books));
$order = $orders[2];
$this->assertEquals(3, $order->id);
$this->assertEquals(1, count($order->books));
$this->assertEquals(2, $order->books[0]->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 table
$order = Order::find(2);
$this->assertEquals(0, count($order->books));
$orderItem = OrderItem::find(array('order_id' => 2, 'item_id' => 1));
$this->assertNull($orderItem);
$item = Item::find(1);
$order->link('books', $item, array('quantity' => 10, 'subtotal' => 100));
$this->assertEquals(1, count($order->books));
$orderItem = OrderItem::find(array('order_id' => 2, 'item_id' => 1));
$this->assertTrue($orderItem instanceof OrderItem);
$this->assertEquals(10, $orderItem->quantity);
$this->assertEquals(100, $orderItem->subtotal);
// via model
$order = Order::find(1);
$this->assertEquals(2, count($order->items));
$this->assertEquals(2, count($order->orderItems));
$orderItem = OrderItem::find(array('order_id' => 1, 'item_id' => 3));
$this->assertNull($orderItem);
$item = Item::find(3);
$order->link('items', $item, array('quantity' => 10, 'subtotal' => 100));
$this->assertEquals(3, count($order->items));
$this->assertEquals(3, count($order->orderItems));
$orderItem = OrderItem::find(array('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));
// via table
$order = Order::find(1);
$this->assertEquals(2, count($order->books));
$order->unlink('books', $order->books[1], true);
$this->assertEquals(1, count($order->books));
$this->assertEquals(1, 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);
}
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);
// updateCounters
$pk = array('order_id' => 2, 'item_id' => 4);
$orderItem = OrderItem::find($pk);
$this->assertEquals(1, $orderItem->quantity);
$ret = $orderItem->updateCounters(array('quantity' => -1));
$this->assertTrue($ret);
$this->assertEquals(0, $orderItem->quantity);
$orderItem = OrderItem::find($pk);
$this->assertEquals(0, $orderItem->quantity);
// updateAll
$customer = Customer::find(3);
$this->assertEquals('user3', $customer->name);
$ret = Customer::updateAll(array(
'name' => 'temp',
), array('id' => 3));
$this->assertEquals(1, $ret);
$customer = Customer::find(3);
$this->assertEquals('temp', $customer->name);
// updateCounters
$pk = array('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 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));
}
}
Loading…
Cancel
Save