From e3df19d984577ac4a53994f6a63855e999f91130 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 26 Aug 2013 19:29:55 +0200 Subject: [PATCH] Redis AR WIP - introduced RecordSchema class for schema definition --- framework/yii/db/ActiveRecord.php | 2 +- framework/yii/db/redis/ActiveQuery.php | 4 +- framework/yii/db/redis/ActiveRecord.php | 53 ++++++++--------- framework/yii/db/redis/Connection.php | 3 +- framework/yii/db/redis/RecordSchema.php | 53 +++++++++++++++++ tests/unit/data/ar/redis/Customer.php | 5 +- tests/unit/data/ar/redis/Item.php | 14 ++--- tests/unit/data/ar/redis/Order.php | 10 +--- tests/unit/data/ar/redis/OrderItem.php | 10 +--- tests/unit/framework/db/redis/ActiveRecordTest.php | 25 +++++++- tests/unit/framework/db/redis/ConnectionTest.php | 66 ---------------------- .../framework/db/redis/RedisConnectionTest.php | 66 ++++++++++++++++++++++ tests/unit/framework/db/redis/RedisTestCase.php | 8 ++- 13 files changed, 193 insertions(+), 126 deletions(-) create mode 100644 framework/yii/db/redis/RecordSchema.php delete mode 100644 tests/unit/framework/db/redis/ConnectionTest.php create mode 100644 tests/unit/framework/db/redis/RedisConnectionTest.php diff --git a/framework/yii/db/ActiveRecord.php b/framework/yii/db/ActiveRecord.php index 5aa9807..9215550 100644 --- a/framework/yii/db/ActiveRecord.php +++ b/framework/yii/db/ActiveRecord.php @@ -268,7 +268,7 @@ class ActiveRecord extends Model */ public static function tableName() { - return 'tbl_' . Inflector::camel2id(StringHelper::basename(get_called_class()), '_'); + return static::getTableSchema()->name; } /** diff --git a/framework/yii/db/redis/ActiveQuery.php b/framework/yii/db/redis/ActiveQuery.php index 1fbde46..1d44d97 100644 --- a/framework/yii/db/redis/ActiveQuery.php +++ b/framework/yii/db/redis/ActiveQuery.php @@ -112,7 +112,7 @@ class ActiveQuery extends \yii\base\Component } $rows = array(); foreach($primaryKeys as $pk) { - $key = $modelClass::tableName() . ':a:' . (is_array($pk) ? implode('-', $pk) : $pk); // TODO escape PK glue + $key = $modelClass::tableName() . ':a:' . $modelClass::hashPk($pk); // get attributes $data = $db->executeCommand('HGETALL', array($key)); $row = array(); @@ -148,7 +148,7 @@ class ActiveQuery extends \yii\base\Component $primaryKeys = $db->executeCommand('LRANGE', array($modelClass::tableName(), $start, $start + 1)); } $pk = reset($primaryKeys); - $key = $modelClass::tableName() . ':a:' . (is_array($pk) ? implode('-', $pk) : $pk); // TODO escape PK glue + $key = $modelClass::tableName() . ':a:' . $modelClass::hashPk($pk); // get attributes $data = $db->executeCommand('HGETALL', array($key)); if ($data === array()) { diff --git a/framework/yii/db/redis/ActiveRecord.php b/framework/yii/db/redis/ActiveRecord.php index 31e8171..b043e21 100644 --- a/framework/yii/db/redis/ActiveRecord.php +++ b/framework/yii/db/redis/ActiveRecord.php @@ -20,7 +20,7 @@ use yii\db\TableSchema; /** * ActiveRecord is the base class for classes representing relational data in terms of objects. * - * @include @yii/db/ActiveRecord.md + * * * @author Carsten Brandt * @since 2.0 @@ -68,31 +68,19 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord return $query; } + public static function hashPk($pk) + { + return (is_array($pk) ? implode('-', $pk) : $pk); // TODO escape PK glue + } + /** - * Creates an [[ActiveQuery]] instance with a given SQL statement. - * - * Note that because the SQL statement is already specified, calling additional - * query modification methods (such as `where()`, `order()`) on the created [[ActiveQuery]] - * instance will have no effect. However, calling `with()`, `asArray()` or `indexBy()` is - * still fine. - * - * Below is an example: - * - * ~~~ - * $customers = Customer::findBySql('SELECT * FROM tbl_customer')->all(); - * ~~~ - * - * @param string $sql the SQL statement to be executed - * @param array $params parameters to be bound to the SQL statement during execution. - * @return ActiveQuery the newly created [[ActiveQuery]] instance + * @inheritdoc */ public static function findBySql($sql, $params = array()) { throw new NotSupportedException('findBySql() is not supported by redis ActiveRecord'); } - - /** * Creates an [[ActiveQuery]] instance. * This method is called by [[find()]], [[findBySql()]] and [[count()]] to start a SELECT query. @@ -107,6 +95,14 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord )); } + /** + * Declares the name of the database table associated with this AR class. + * @return string the table name + */ + public static function tableName() + { + return static::getTableSchema()->name; + } /** * Returns the schema information of the DB table associated with this AR class. @@ -114,6 +110,7 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord */ public static function getTableSchema() { + // TODO should be cached throw new InvalidConfigException(__CLASS__.'::getTableSchema() needs to be overridden in subclasses and return a TableSchema.'); } @@ -173,9 +170,9 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord } // } // save pk in a findall pool - $db->executeCommand('RPUSH', array(static::tableName(), implode('-', $pk))); // TODO escape PK glue + $db->executeCommand('RPUSH', array(static::tableName(), static::hashPk($pk))); - $key = static::tableName() . ':a:' . implode('-', $pk); // TODO escape PK glue + $key = static::tableName() . ':a:' . static::hashPk($pk); // save attributes $args = array($key); foreach($values as $attribute => $value) { @@ -216,7 +213,7 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord } $n=0; foreach($condition as $pk) { - $key = static::tableName() . ':a:' . (is_array($pk) ? implode('-', $pk) : $pk); // TODO escape PK glue + $key = static::tableName() . ':a:' . static::hashPk($pk); // save attributes $args = array($key); foreach($attributes as $attribute => $value) { @@ -257,7 +254,7 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord } $n=0; 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:' . static::hashPk($pk); foreach($counters as $attribute => $value) { $db->executeCommand('HINCRBY', array($key, $attribute, $value)); } @@ -292,13 +289,11 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord } $attributeKeys = array(); foreach($condition as $pk) { - if (is_array($pk)) { - $pk = implode('-', $pk); - } - $db->executeCommand('LREM', array(static::tableName(), 0, $pk)); // TODO escape PK glue - $attributeKeys[] = static::tableName() . ':a:' . $pk; // TODO escape PK glue + $pk = static::hashPk($pk); + $db->executeCommand('LREM', array(static::tableName(), 0, $pk)); + $attributeKeys[] = static::tableName() . ':a:' . $pk; } - return $db->executeCommand('DEL', $attributeKeys); + return $db->executeCommand('DEL', $attributeKeys);// TODO make this atomic or document as NOT } /** diff --git a/framework/yii/db/redis/Connection.php b/framework/yii/db/redis/Connection.php index b99c52b..46db575 100644 --- a/framework/yii/db/redis/Connection.php +++ b/framework/yii/db/redis/Connection.php @@ -12,6 +12,7 @@ namespace yii\db\redis; use \yii\base\Component; use yii\base\InvalidConfigException; use \yii\db\Exception; +use yii\helpers\Inflector; use yii\helpers\StringHelper; /** @@ -337,7 +338,7 @@ class Connection extends Component */ public function __call($name, $params) { - $redisCommand = strtoupper(StringHelper::camel2words($name, false)); + $redisCommand = strtoupper(Inflector::camel2words($name, false)); if (in_array($redisCommand, $this->redisCommands)) { return $this->executeCommand($name, $params); } else { diff --git a/framework/yii/db/redis/RecordSchema.php b/framework/yii/db/redis/RecordSchema.php new file mode 100644 index 0000000..3bc219d --- /dev/null +++ b/framework/yii/db/redis/RecordSchema.php @@ -0,0 +1,53 @@ + + */ + +namespace yii\db\redis; + + +use yii\base\InvalidConfigException; +use yii\db\TableSchema; + +/** + * Class RecordSchema defines the data schema for a redis active record + * + * As there is no schema in a redis DB this class is used to define one. + * + * @package yii\db\redis + */ +class RecordSchema extends TableSchema +{ + /** + * @var string[] column names. + */ + public $columns = array(); + + /** + * @return string the column type + */ + public function getColumn($name) + { + parent::getColumn($name); + } + + public function init() + { + if (empty($this->name)) { + throw new InvalidConfigException('name of RecordSchema must not be empty.'); + } + if (empty($this->primaryKey)) { + throw new InvalidConfigException('primaryKey of RecordSchema must not be empty.'); + } + if (!is_array($this->primaryKey)) { + $this->primaryKey = array($this->primaryKey); + } + foreach($this->primaryKey as $pk) { + if (!isset($this->columns[$pk])) { + throw new InvalidConfigException('primaryKey '.$pk.' is not a colum of RecordSchema.'); + } + } + } +} \ No newline at end of file diff --git a/tests/unit/data/ar/redis/Customer.php b/tests/unit/data/ar/redis/Customer.php index 9e7ea62..91a75ff 100644 --- a/tests/unit/data/ar/redis/Customer.php +++ b/tests/unit/data/ar/redis/Customer.php @@ -2,7 +2,7 @@ namespace yiiunit\data\ar\redis; -use yii\db\TableSchema; +use yii\db\redis\RecordSchema; class Customer extends ActiveRecord { @@ -21,7 +21,8 @@ class Customer extends ActiveRecord public static function getTableSchema() { - return new TableSchema(array( + return new RecordSchema(array( + 'name' => 'customer', 'primaryKey' => array('id'), 'columns' => array( 'id' => 'integer', diff --git a/tests/unit/data/ar/redis/Item.php b/tests/unit/data/ar/redis/Item.php index 6dbaa2f..55d1420 100644 --- a/tests/unit/data/ar/redis/Item.php +++ b/tests/unit/data/ar/redis/Item.php @@ -2,19 +2,19 @@ namespace yiiunit\data\ar\redis; -use yii\db\TableSchema; +use yii\db\redis\RecordSchema; class Item extends ActiveRecord { - public static function tableName() - { - return 'tbl_item'; - } - public static function getTableSchema() { - return new TableSchema(array( + return new RecordSchema(array( + 'name' => 'item', 'primaryKey' => array('id'), + 'sequenceName' => 'id', + 'foreignKeys' => array( + // TODO for defining relations + ), 'columns' => array( 'id' => 'integer', 'name' => 'string', diff --git a/tests/unit/data/ar/redis/Order.php b/tests/unit/data/ar/redis/Order.php index d97a3af..8ccb12e 100644 --- a/tests/unit/data/ar/redis/Order.php +++ b/tests/unit/data/ar/redis/Order.php @@ -2,15 +2,10 @@ namespace yiiunit\data\ar\redis; -use yii\db\TableSchema; +use yii\db\redis\RecordSchema; class Order extends ActiveRecord { - public static function tableName() - { - return 'tbl_order'; - } - public function getCustomer() { return $this->hasOne('Customer', array('id' => 'customer_id')); @@ -49,7 +44,8 @@ class Order extends ActiveRecord public static function getTableSchema() { - return new TableSchema(array( + return new RecordSchema(array( + 'name' => 'orders', 'primaryKey' => array('id'), 'columns' => array( 'id' => 'integer', diff --git a/tests/unit/data/ar/redis/OrderItem.php b/tests/unit/data/ar/redis/OrderItem.php index 257b9b0..f0719b9 100644 --- a/tests/unit/data/ar/redis/OrderItem.php +++ b/tests/unit/data/ar/redis/OrderItem.php @@ -2,15 +2,10 @@ namespace yiiunit\data\ar\redis; -use yii\db\TableSchema; +use yii\db\redis\RecordSchema; class OrderItem extends ActiveRecord { - public static function tableName() - { - return 'tbl_order_item'; - } - public function getOrder() { return $this->hasOne('Order', array('id' => 'order_id')); @@ -23,7 +18,8 @@ class OrderItem extends ActiveRecord public static function getTableSchema() { - return new TableSchema(array( + return new RecordSchema(array( + 'name' => 'order_item', 'primaryKey' => array('order_id', 'item_id'), 'columns' => array( 'order_id' => 'integer', diff --git a/tests/unit/framework/db/redis/ActiveRecordTest.php b/tests/unit/framework/db/redis/ActiveRecordTest.php index 4d7aea2..064a6d9 100644 --- a/tests/unit/framework/db/redis/ActiveRecordTest.php +++ b/tests/unit/framework/db/redis/ActiveRecordTest.php @@ -9,10 +9,31 @@ use yiiunit\data\ar\redis\OrderItem; use yiiunit\data\ar\redis\Order; use yiiunit\data\ar\redis\Item; +/* +Users: +1 - user1 +2 - user2 +3 - user3 + +Items: 1-5 + +Order: 1-3 + +OrderItem: +1 - order: 1 +2 - order: 1 +3 - order: 2 +4 - order: 2 +5 - order: 2 +6 - order: 3 + + */ + class ActiveRecordTest extends RedisTestCase { public function setUp() { + parent::setUp(); ActiveRecord::$db = $this->getConnection(); $customer = new Customer(); @@ -72,8 +93,6 @@ class ActiveRecordTest extends RedisTestCase $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() @@ -332,6 +351,8 @@ class ActiveRecordTest extends RedisTestCase $this->assertFalse($customer->isNewRecord); } + // TODO test serial column incr + public function testUpdate() { // save diff --git a/tests/unit/framework/db/redis/ConnectionTest.php b/tests/unit/framework/db/redis/ConnectionTest.php deleted file mode 100644 index ab66e1d..0000000 --- a/tests/unit/framework/db/redis/ConnectionTest.php +++ /dev/null @@ -1,66 +0,0 @@ -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')); - } -} \ No newline at end of file diff --git a/tests/unit/framework/db/redis/RedisConnectionTest.php b/tests/unit/framework/db/redis/RedisConnectionTest.php new file mode 100644 index 0000000..85c69ac --- /dev/null +++ b/tests/unit/framework/db/redis/RedisConnectionTest.php @@ -0,0 +1,66 @@ +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')); + } +} \ No newline at end of file diff --git a/tests/unit/framework/db/redis/RedisTestCase.php b/tests/unit/framework/db/redis/RedisTestCase.php index 7c9ee3e..309ecc5 100644 --- a/tests/unit/framework/db/redis/RedisTestCase.php +++ b/tests/unit/framework/db/redis/RedisTestCase.php @@ -12,7 +12,10 @@ class RedisTestCase extends TestCase { protected function setUp() { - $params = $this->getParam('redis'); + $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.'); } @@ -34,7 +37,8 @@ class RedisTestCase extends TestCase */ public function getConnection($reset = true) { - $params = $this->getParam('redis'); + $databases = $this->getParam('databases'); + $params = isset($databases['redis']) ? $databases['redis'] : array(); $db = new \yii\db\redis\Connection; $db->dsn = $params['dsn']; $db->password = $params['password'];