From 39a1ce406b73896a20ea2a7be28cc1686c05a3ab Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 28 Mar 2013 15:41:03 +0100 Subject: [PATCH] Redis concept finished except relations --- framework/db/ActiveRelation.php | 2 +- framework/db/redis/ActiveQuery.php | 288 +++++++++++++++-- framework/db/redis/ActiveRecord.php | 14 +- framework/db/redis/ActiveRelation.php | 2 +- framework/db/redis/Command.php | 567 ---------------------------------- framework/db/redis/Connection.php | 237 ++++++++++---- 6 files changed, 454 insertions(+), 656 deletions(-) delete mode 100644 framework/db/redis/Command.php diff --git a/framework/db/ActiveRelation.php b/framework/db/ActiveRelation.php index 54c6c62..4d87fb3 100644 --- a/framework/db/ActiveRelation.php +++ b/framework/db/ActiveRelation.php @@ -45,7 +45,7 @@ class ActiveRelation extends ActiveQuery * @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 they will be done automatically by Yii. + * Do not prefix or quote the column names as this will be done automatically by Yii. */ public $link; /** diff --git a/framework/db/redis/ActiveQuery.php b/framework/db/redis/ActiveQuery.php index e24e0f3..abc3f87 100644 --- a/framework/db/redis/ActiveQuery.php +++ b/framework/db/redis/ActiveQuery.php @@ -11,20 +11,82 @@ namespace yii\db\redis; /** - * ActiveRecord is the base class for classes representing relational data in terms of objects. + * ActiveQuery represents a DB query associated with an Active Record class. * + * ActiveQuery instances are usually created by [[yii\db\redis\ActiveRecord::find()]] + * and [[yii\db\redis\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 [[limit()]], [[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 * @since 2.0 */ -class ActiveQuery extends \yii\db\ActiveQuery +class ActiveQuery extends \yii\base\Component { /** + * @var string the name of the ActiveRecord class. + */ + public $modelClass; + /** + * @var array list of relations that this query should be performed with + */ + public $with; + /** + * @var string the name of the column by which query results should be indexed by. + * This is only used when the query result is returned as an array when calling [[all()]]. + */ + public $indexBy; + /** + * @var boolean whether to return each record as an array. If false (default), an object + * of [[modelClass]] will be created to represent each record. + */ + public $asArray; + /** + * @var integer maximum number of records to be returned. If not set or less than 0, it means no limit. + */ + public $limit; + /** + * @var integer zero-based offset from where the records are to be returned. If not set or + * less than 0, it means starting from the beginning. + */ + public $offset; + /** + * @var string|array how to sort the query results. This refers to the ORDER BY clause in a SQL statement. + * It can be either a string (e.g. `'id ASC, name DESC'`) or an array (e.g. `array('id ASC', 'name DESC')`). + */ + public $orderBy; + + /** * Executes query and returns all results as an array. * @return array the query results. If the query results in nothing, an empty array will be returned. */ public function all() { + // TODO implement $command = $this->createCommand(); $rows = $command->queryAll(); if ($rows !== array()) { @@ -46,6 +108,7 @@ class ActiveQuery extends \yii\db\ActiveQuery */ public function one() { + // TODO implement $command = $this->createCommand(); $row = $command->queryRow(); if ($row !== false && !$this->asArray) { @@ -71,6 +134,7 @@ class ActiveQuery extends \yii\db\ActiveQuery */ public function count($q = '*') { + // TODO implement $this->select = array("COUNT($q)"); return $this->createCommand()->queryScalar(); } @@ -83,6 +147,7 @@ class ActiveQuery extends \yii\db\ActiveQuery */ public function sum($q) { + // TODO implement $this->select = array("SUM($q)"); return $this->createCommand()->queryScalar(); } @@ -95,6 +160,7 @@ class ActiveQuery extends \yii\db\ActiveQuery */ public function average($q) { + // TODO implement $this->select = array("AVG($q)"); return $this->createCommand()->queryScalar(); } @@ -107,6 +173,7 @@ class ActiveQuery extends \yii\db\ActiveQuery */ public function min($q) { + // TODO implement $this->select = array("MIN($q)"); return $this->createCommand()->queryScalar(); } @@ -119,6 +186,7 @@ class ActiveQuery extends \yii\db\ActiveQuery */ public function max($q) { + // TODO implement $this->select = array("MAX($q)"); return $this->createCommand()->queryScalar(); } @@ -131,6 +199,7 @@ class ActiveQuery extends \yii\db\ActiveQuery */ public function scalar() { + // TODO implement return $this->createCommand()->queryScalar(); } @@ -140,33 +209,214 @@ class ActiveQuery extends \yii\db\ActiveQuery */ public function exists() { + // TODO implement $this->select = array(new Expression('1')); return $this->scalar() !== false; } + + /** + * Sets the [[asArray]] property. + * TODO: refactor, it is duplicated from yii/db/ActiveQuery + * @param boolean $value whether to return the query results in terms of arrays instead of Active Records. + * @return ActiveQuery the query object itself + */ + public function asArray($value = true) + { + $this->asArray = $value; + return $this; + } + + /** + * Sets the ORDER BY part of the query. + * TODO: refactor, it is duplicated from yii/db/Query + * @param string|array $columns the columns (and the directions) to be ordered by. + * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')). + * The method will automatically quote the column names unless a column contains some parenthesis + * (which means the column contains a DB expression). + * @return Query the query object itself + * @see addOrder() + */ + public function orderBy($columns) + { + $this->orderBy = $columns; + return $this; + } + /** - * Creates a DB command that can be used to execute this query. - * @param Connection $db the DB connection used to create the DB command. - * If null, the DB connection returned by [[modelClass]] will be used. - * @return Command the created DB command instance. + * Adds additional ORDER BY columns to the query. + * TODO: refactor, it is duplicated from yii/db/Query + * @param string|array $columns the columns (and the directions) to be ordered by. + * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')). + * The method will automatically quote the column names unless a column contains some parenthesis + * (which means the column contains a DB expression). + * @return Query the query object itself + * @see order() */ - public function createCommand($db = null) + public function addOrderBy($columns) { - /** @var $modelClass ActiveRecord */ - $modelClass = $this->modelClass; - if ($db === null) { - $db = $modelClass::getDb(); + if (empty($this->orderBy)) { + $this->orderBy = $columns; + } else { + if (!is_array($this->orderBy)) { + $this->orderBy = preg_split('/\s*,\s*/', trim($this->orderBy), -1, PREG_SPLIT_NO_EMPTY); + } + if (!is_array($columns)) { + $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); + } + $this->orderBy = array_merge($this->orderBy, $columns); } - if ($this->sql === null) { - if ($this->from === null) { - $tableName = $modelClass::tableName(); - $this->from = array($tableName); + return $this; + } + + /** + * Sets the LIMIT part of the query. + * TODO: refactor, it is duplicated from yii/db/Query + * @param integer $limit the limit + * @return Query the query object itself + */ + public function limit($limit) + { + $this->limit = $limit; + return $this; + } + + /** + * Sets the OFFSET part of the query. + * TODO: refactor, it is duplicated from yii/db/Query + * @param integer $offset the offset + * @return Query the query object itself + */ + public function offset($offset) + { + $this->offset = $offset; + return $this; + } + + /** + * Specifies the relations with which this query should be performed. + * + * The parameters to this method can be either one or multiple strings, or a single array + * of relation names and the optional callbacks to customize the relations. + * + * The followings are some usage examples: + * + * ~~~ + * // find customers together with their orders and country + * Customer::find()->with('orders', 'country')->all(); + * // find customers together with their country and orders of status 1 + * Customer::find()->with(array( + * 'orders' => function($query) { + * $query->andWhere('status = 1'); + * }, + * 'country', + * ))->all(); + * ~~~ + * + * TODO: refactor, it is duplicated from yii/db/ActiveQuery + * @return ActiveQuery the query object itself + */ + public function with() + { + $this->with = func_get_args(); + if (isset($this->with[0]) && is_array($this->with[0])) { + // the parameter is given as an array + $this->with = $this->with[0]; + } + return $this; + } + + /** + * Sets the [[indexBy]] property. + * TODO: refactor, it is duplicated from yii/db/ActiveQuery + * @param string $column the name of the column by which the query results should be indexed by. + * @return ActiveQuery the query object itself + */ + public function indexBy($column) + { + $this->indexBy = $column; + return $this; + } + + // TODO: refactor, it is duplicated from yii/db/ActiveQuery + private function createModels($rows) + { + $models = array(); + if ($this->asArray) { + if ($this->indexBy === null) { + return $rows; + } + foreach ($rows as $row) { + $models[$row[$this->indexBy]] = $row; } - /** @var $qb QueryBuilder */ - $qb = $db->getQueryBuilder(); - $this->sql = $qb->build($this); + } else { + /** @var $class ActiveRecord */ + $class = $this->modelClass; + if ($this->indexBy === null) { + foreach ($rows as $row) { + $models[] = $class::create($row); + } + } else { + foreach ($rows as $row) { + $model = $class::create($row); + $models[$model->{$this->indexBy}] = $model; + } + } + } + return $models; + } + + // TODO: refactor, it is duplicated from yii/db/ActiveQuery + private function populateRelations(&$models, $with) + { + $primaryModel = new $this->modelClass; + $relations = $this->normalizeRelations($primaryModel, $with); + foreach ($relations as $name => $relation) { + if ($relation->asArray === null) { + // inherit asArray from primary query + $relation->asArray = $this->asArray; + } + $relation->findWith($name, $models); } - return $db->createCommand($this->sql, $this->params); } + /** + * TODO: refactor, it is duplicated from yii/db/ActiveQuery + * @param ActiveRecord $model + * @param array $with + * @return ActiveRelation[] + */ + private function normalizeRelations($model, $with) + { + $relations = array(); + foreach ($with as $name => $callback) { + if (is_integer($name)) { + $name = $callback; + $callback = null; + } + if (($pos = strpos($name, '.')) !== false) { + // with sub-relations + $childName = substr($name, $pos + 1); + $name = substr($name, 0, $pos); + } else { + $childName = null; + } + + $t = strtolower($name); + if (!isset($relations[$t])) { + $relation = $model->getRelation($name); + $relation->primaryModel = null; + $relations[$t] = $relation; + } else { + $relation = $relations[$t]; + } + + if (isset($childName)) { + $relation->with[$childName] = $callback; + } elseif ($callback !== null) { + call_user_func($callback, $relation); + } + } + return $relations; + } } diff --git a/framework/db/redis/ActiveRecord.php b/framework/db/redis/ActiveRecord.php index 001a2f7..dda81eb 100644 --- a/framework/db/redis/ActiveRecord.php +++ b/framework/db/redis/ActiveRecord.php @@ -10,6 +10,8 @@ namespace yii\db\redis; +use yii\base\NotSupportedException; + /** * ActiveRecord is the base class for classes representing relational data in terms of objects. * @@ -25,7 +27,7 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord * 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() + public function attributes() // TODO: refactor should be abstract in an ActiveRecord base class { return array(); } @@ -93,9 +95,7 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord */ public static function findBySql($sql, $params = array()) { - $query = static::createQuery(); - $query->sql = $sql; - return $query->params($params); + throw new NotSupportedException('findBySql() is not supported by redis ActiveRecord'); } @@ -121,7 +121,7 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord */ public static function getTableSchema() { - return static::getDb()->getTableSchema(static::tableName()); + 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'); } /** @@ -137,9 +137,9 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord * * @return string[] the primary keys of the associated database table. */ - public static function primaryKey() + public static function primaryKey() // TODO: refactor should be abstract in an ActiveRecord base class { - return static::getTableSchema()->primaryKey; + return array(); } } diff --git a/framework/db/redis/ActiveRelation.php b/framework/db/redis/ActiveRelation.php index a205cd0..d3d5c5a 100644 --- a/framework/db/redis/ActiveRelation.php +++ b/framework/db/redis/ActiveRelation.php @@ -19,5 +19,5 @@ namespace yii\db\redis; */ class ActiveRelation extends \yii\db\ActiveRelation { - + // TODO implement } diff --git a/framework/db/redis/Command.php b/framework/db/redis/Command.php deleted file mode 100644 index 714f012..0000000 --- a/framework/db/redis/Command.php +++ /dev/null @@ -1,567 +0,0 @@ - - * @since 2.0 - */ -class Command extends \yii\base\Component -{ - /** - * @var Connection the DB connection that this command is associated with - */ - public $db; - /** - * @var array the parameter log information (name=>value) - */ - private $_params = array(); - - private $_query; - - - /** - * Determines the PDO type for the give PHP data value. - * @param mixed $data the data whose PDO type is to be determined - * @return integer the PDO type - * @see http://www.php.net/manual/en/pdo.constants.php - */ - private function getRedisType($data) - { - static $typeMap = array( - 'boolean' => \PDO::PARAM_BOOL, - 'integer' => \PDO::PARAM_INT, - 'string' => \PDO::PARAM_STR, - 'NULL' => \PDO::PARAM_NULL, - ); - $type = gettype($data); - return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; - } - - /** - * Executes the SQL statement. - * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs. - * No result set will be returned. - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] - * or [[bindValue()]] will be ignored. - * @return integer number of rows affected by the execution. - * @throws Exception execution failed - */ - public function execute() - { - $query = $this->_query; - - if ($this->_params === array()) { - $paramLog = ''; - } else { - $paramLog = "\nParameters: " . var_export($this->_params, true); - } - - \Yii::trace("Executing SQL: {$query}{$paramLog}", __CLASS__); - - if ($query == '') { - return 0; - } - - try { - if ($this->db->enableProfiling) { - \Yii::beginProfile(__METHOD__ . "($query)", __CLASS__); - } - - $n = $this->db->redis->send_command(array_merge(array($query), $this->_params)); - - if ($this->db->enableProfiling) { - \Yii::endProfile(__METHOD__ . "($query)", __CLASS__); - } - return $n; - } catch (\Exception $e) { - if ($this->db->enableProfiling) { - \Yii::endProfile(__METHOD__ . "($query)", __CLASS__); - } - $message = $e->getMessage(); - - \Yii::error("$message\nFailed to execute SQL: {$query}{$paramLog}", __CLASS__); - - throw new Exception($message, (int)$e->getCode()); - } - } - - /** - * Executes the SQL statement and returns query result. - * This method is for executing a SQL query that returns result set, such as `SELECT`. - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] - * or [[bindValue()]] will be ignored. - * @return DataReader the reader object for fetching the query result - * @throws Exception execution failed - */ - public function query($params = array()) - { - return $this->queryInternal('', $params); - } - - /** - * Executes the SQL statement and returns ALL rows at once. - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] - * or [[bindValue()]] will be ignored. - * @param mixed $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) - * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used. - * @return array all rows of the query result. Each array element is an array representing a row of data. - * An empty array is returned if the query results in nothing. - * @throws Exception execution failed - */ - public function queryAll($params = array(), $fetchMode = null) - { - return $this->queryInternal('fetchAll', $params, $fetchMode); - } - - /** - * Executes the SQL statement and returns the first row of the result. - * This method is best used when only the first row of result is needed for a query. - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] - * or [[bindValue()]] will be ignored. - * @param mixed $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) - * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used. - * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query - * results in nothing. - * @throws Exception execution failed - */ - public function queryRow($params = array(), $fetchMode = null) - { - return $this->queryInternal('fetch', $params, $fetchMode); - } - - /** - * Executes the SQL statement and returns the value of the first column in the first row of data. - * This method is best used when only a single value is needed for a query. - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] - * or [[bindValue()]] will be ignored. - * @return string|boolean the value of the first column in the first row of the query result. - * False is returned if there is no value. - * @throws Exception execution failed - */ - public function queryScalar($params = array()) - { - $result = $this->queryInternal('fetchColumn', $params, 0); - if (is_resource($result) && get_resource_type($result) === 'stream') { - return stream_get_contents($result); - } else { - return $result; - } - } - - /** - * Executes the SQL statement and returns the first column of the result. - * This method is best used when only the first column of result (i.e. the first element in each row) - * is needed for a query. - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] - * or [[bindValue()]] will be ignored. - * @return array the first column of the query result. Empty array is returned if the query results in nothing. - * @throws Exception execution failed - */ - public function queryColumn($params = array()) - { - return $this->queryInternal('fetchAll', $params, \PDO::FETCH_COLUMN); - } - - /** - * Performs the actual DB query of a SQL statement. - * @param string $method method of PDOStatement to be called - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] - * or [[bindValue()]] will be ignored. - * @param mixed $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) - * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used. - * @return mixed the method execution result - * @throws Exception if the query causes any problem - */ - private function queryInternal($method, $params, $fetchMode = null) - { - $db = $this->db; - $sql = $this->getSql(); - $this->_params = array_merge($this->_params, $params); - if ($this->_params === array()) { - $paramLog = ''; - } else { - $paramLog = "\nParameters: " . var_export($this->_params, true); - } - - \Yii::trace("Querying SQL: {$sql}{$paramLog}", __CLASS__); - - /** @var $cache \yii\caching\Cache */ - if ($db->enableQueryCache && $method !== '') { - $cache = \Yii::$application->getComponent($db->queryCacheID); - } - - if (isset($cache)) { - $cacheKey = $cache->buildKey(__CLASS__, $db->dsn, $db->username, $sql, $paramLog); - if (($result = $cache->get($cacheKey)) !== false) { - \Yii::trace('Query result found in cache', __CLASS__); - return $result; - } - } - - try { - if ($db->enableProfiling) { - \Yii::beginProfile(__METHOD__ . "($sql)", __CLASS__); - } - - $this->prepare(); - if ($params === array()) { - $this->pdoStatement->execute(); - } else { - $this->pdoStatement->execute($params); - } - - if ($method === '') { - $result = new DataReader($this); - } else { - if ($fetchMode === null) { - $fetchMode = $this->fetchMode; - } - $result = call_user_func_array(array($this->pdoStatement, $method), (array)$fetchMode); - $this->pdoStatement->closeCursor(); - } - - if ($db->enableProfiling) { - \Yii::endProfile(__METHOD__ . "($sql)", __CLASS__); - } - - if (isset($cache, $cacheKey)) { - $cache->set($cacheKey, $result, $db->queryCacheDuration, $db->queryCacheDependency); - \Yii::trace('Saved query result in cache', __CLASS__); - } - - return $result; - } catch (\Exception $e) { - if ($db->enableProfiling) { - \Yii::endProfile(__METHOD__ . "($sql)", __CLASS__); - } - $message = $e->getMessage(); - \Yii::error("$message\nCommand::$method() failed: {$sql}{$paramLog}", __CLASS__); - $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; - throw new Exception($message, (int)$e->getCode(), $errorInfo); - } - } - - /** - * Creates an INSERT command. - * For example, - * - * ~~~ - * $connection->createCommand()->insert('tbl_user', array( - * 'name' => 'Sam', - * 'age' => 30, - * ))->execute(); - * ~~~ - * - * The method will properly escape the column names, and bind the values to be inserted. - * - * Note that the created command is not executed until [[execute()]] is called. - * - * @param string $table the table that new rows will be inserted into. - * @param array $columns the column data (name=>value) to be inserted into the table. - * @return Command the command object itself - */ - public function insert($table, $columns) - { - $params = array(); - $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params); - return $this->setSql($sql)->bindValues($params); - } - - /** - * Creates a batch INSERT command. - * For example, - * - * ~~~ - * $connection->createCommand()->batchInsert('tbl_user', array('name', 'age'), array( - * array('Tom', 30), - * array('Jane', 20), - * array('Linda', 25), - * ))->execute(); - * ~~~ - * - * Not that the values in each row must match the corresponding column names. - * - * @param string $table the table that new rows will be inserted into. - * @param array $columns the column names - * @param array $rows the rows to be batch inserted into the table - * @return Command the command object itself - */ - public function batchInsert($table, $columns, $rows) - { - $sql = $this->db->getQueryBuilder()->batchInsert($table, $columns, $rows); - return $this->setSql($sql); - } - - /** - * Creates an UPDATE command. - * For example, - * - * ~~~ - * $connection->createCommand()->update('tbl_user', array( - * 'status' => 1, - * ), 'age > 30')->execute(); - * ~~~ - * - * The method will properly escape the column names and bind the values to be updated. - * - * Note that the created command is not executed until [[execute()]] is called. - * - * @param string $table the table to be updated. - * @param array $columns the column data (name=>value) to be updated. - * @param mixed $condition the condition that will be put in the WHERE part. Please - * refer to [[Query::where()]] on how to specify condition. - * @param array $params the parameters to be bound to the command - * @return Command the command object itself - */ - public function update($table, $columns, $condition = '', $params = array()) - { - $sql = $this->db->getQueryBuilder()->update($table, $columns, $condition, $params); - return $this->setSql($sql)->bindValues($params); - } - - /** - * Creates a DELETE command. - * For example, - * - * ~~~ - * $connection->createCommand()->delete('tbl_user', 'status = 0')->execute(); - * ~~~ - * - * The method will properly escape the table and column names. - * - * Note that the created command is not executed until [[execute()]] is called. - * - * @param string $table the table where the data will be deleted from. - * @param mixed $condition the condition that will be put in the WHERE part. Please - * refer to [[Query::where()]] on how to specify condition. - * @param array $params the parameters to be bound to the command - * @return Command the command object itself - */ - public function delete($table, $condition = '', $params = array()) - { - $sql = $this->db->getQueryBuilder()->delete($table, $condition); - return $this->setSql($sql)->bindValues($params); - } - - - /** - * Creates a SQL command for creating a new DB table. - * - * The columns in the new table should be specified as name-definition pairs (e.g. 'name'=>'string'), - * where name stands for a column name which will be properly quoted by the method, and definition - * stands for the column type which can contain an abstract DB type. - * The method [[QueryBuilder::getColumnType()]] will be called - * to convert the abstract column types to physical ones. For example, `string` will be converted - * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`. - * - * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly - * inserted into the generated SQL. - * - * @param string $table the name of the table to be created. The name will be properly quoted by the method. - * @param array $columns the columns (name=>definition) in the new table. - * @param string $options additional SQL fragment that will be appended to the generated SQL. - * @return Command the command object itself - */ - public function createTable($table, $columns, $options = null) - { - $sql = $this->db->getQueryBuilder()->createTable($table, $columns, $options); - return $this->setSql($sql); - } - - /** - * Creates a SQL command for renaming a DB table. - * @param string $table the table to be renamed. The name will be properly quoted by the method. - * @param string $newName the new table name. The name will be properly quoted by the method. - * @return Command the command object itself - */ - public function renameTable($table, $newName) - { - $sql = $this->db->getQueryBuilder()->renameTable($table, $newName); - return $this->setSql($sql); - } - - /** - * Creates a SQL command for dropping a DB table. - * @param string $table the table to be dropped. The name will be properly quoted by the method. - * @return Command the command object itself - */ - public function dropTable($table) - { - $sql = $this->db->getQueryBuilder()->dropTable($table); - return $this->setSql($sql); - } - - /** - * Creates a SQL command for truncating a DB table. - * @param string $table the table to be truncated. The name will be properly quoted by the method. - * @return Command the command object itself - */ - public function truncateTable($table) - { - $sql = $this->db->getQueryBuilder()->truncateTable($table); - return $this->setSql($sql); - } - - /** - * Creates a SQL command for adding a new DB column. - * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method. - * @param string $column the name of the new column. The name will be properly quoted by the method. - * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called - * to convert the give column type to the physical one. For example, `string` will be converted - * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`. - * @return Command the command object itself - */ - public function addColumn($table, $column, $type) - { - $sql = $this->db->getQueryBuilder()->addColumn($table, $column, $type); - return $this->setSql($sql); - } - - /** - * Creates a SQL command for dropping a DB column. - * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method. - * @param string $column the name of the column to be dropped. The name will be properly quoted by the method. - * @return Command the command object itself - */ - public function dropColumn($table, $column) - { - $sql = $this->db->getQueryBuilder()->dropColumn($table, $column); - return $this->setSql($sql); - } - - /** - * Creates a SQL command for renaming a column. - * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. - * @param string $oldName the old name of the column. The name will be properly quoted by the method. - * @param string $newName the new name of the column. The name will be properly quoted by the method. - * @return Command the command object itself - */ - public function renameColumn($table, $oldName, $newName) - { - $sql = $this->db->getQueryBuilder()->renameColumn($table, $oldName, $newName); - return $this->setSql($sql); - } - - /** - * Creates a SQL command for changing the definition of a column. - * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. - * @param string $column the name of the column to be changed. The name will be properly quoted by the method. - * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called - * to convert the give column type to the physical one. For example, `string` will be converted - * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`. - * @return Command the command object itself - */ - public function alterColumn($table, $column, $type) - { - $sql = $this->db->getQueryBuilder()->alterColumn($table, $column, $type); - return $this->setSql($sql); - } - - /** - * Creates a SQL command for adding a foreign key constraint to an existing table. - * The method will properly quote the table and column names. - * @param string $name the name of the foreign key constraint. - * @param string $table the table that the foreign key constraint will be added to. - * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas. - * @param string $refTable the table that the foreign key references to. - * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas. - * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL - * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL - * @return Command the command object itself - */ - public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) - { - $sql = $this->db->getQueryBuilder()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update); - return $this->setSql($sql); - } - - /** - * Creates a SQL command for dropping a foreign key constraint. - * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method. - * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method. - * @return Command the command object itself - */ - public function dropForeignKey($name, $table) - { - $sql = $this->db->getQueryBuilder()->dropForeignKey($name, $table); - return $this->setSql($sql); - } - - /** - * Creates a SQL command for creating a new index. - * @param string $name the name of the index. The name will be properly quoted by the method. - * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method. - * @param string $columns the column(s) that should be included in the index. If there are multiple columns, please separate them - * by commas. The column names will be properly quoted by the method. - * @param boolean $unique whether to add UNIQUE constraint on the created index. - * @return Command the command object itself - */ - public function createIndex($name, $table, $columns, $unique = false) - { - $sql = $this->db->getQueryBuilder()->createIndex($name, $table, $columns, $unique); - return $this->setSql($sql); - } - - /** - * Creates a SQL command for dropping an index. - * @param string $name the name of the index to be dropped. The name will be properly quoted by the method. - * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method. - * @return Command the command object itself - */ - public function dropIndex($name, $table) - { - $sql = $this->db->getQueryBuilder()->dropIndex($name, $table); - return $this->setSql($sql); - } - - /** - * Creates a SQL command for resetting the sequence value of a table's primary key. - * The sequence will be reset such that the primary key of the next new row inserted - * will have the specified value or 1. - * @param string $table the name of the table whose primary key sequence will be reset - * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, - * the next new row's primary key will have a value 1. - * @return Command the command object itself - * @throws NotSupportedException if this is not supported by the underlying DBMS - */ - public function resetSequence($table, $value = null) - { - $sql = $this->db->getQueryBuilder()->resetSequence($table, $value); - return $this->setSql($sql); - } - - /** - * Builds a SQL command for enabling or disabling integrity check. - * @param boolean $check whether to turn on or off the integrity check. - * @param string $schema the schema name of the tables. Defaults to empty string, meaning the current - * or default schema. - * @return Command the command object itself - * @throws NotSupportedException if this is not supported by the underlying DBMS - */ - public function checkIntegrity($check = true, $schema = '') - { - $sql = $this->db->getQueryBuilder()->checkIntegrity($check, $schema); - return $this->setSql($sql); - } -} diff --git a/framework/db/redis/Connection.php b/framework/db/redis/Connection.php index 8623b22..f4b4e4a 100644 --- a/framework/db/redis/Connection.php +++ b/framework/db/redis/Connection.php @@ -42,12 +42,6 @@ class Connection extends Component * @var string the password for establishing DB connection. Defaults to empty string. */ public $password = ''; - - /** - * @var \Jamm\Memory\RedisServer - */ - public $redis; - /** * @var boolean whether to enable profiling for the SQL statements being executed. * Defaults to false. This should be mainly enabled and used during development @@ -64,10 +58,159 @@ class Connection extends Component * @see enableAutoQuoting */ public $keyPrefix; + + /** + * @var array http://redis.io/commands + */ + public $redisCommands = array( + 'BRPOP', // key [key ...] timeout Remove and get the last element in a list, or block until one is available + 'BRPOPLPUSH', // source destination timeout Pop a value from a list, push it to another list and return it; or block until one is available + 'CLIENT KILL', // ip:port Kill the connection of a client + 'CLIENT LIST', // Get the list of client connections + 'CLIENT GETNAME', // Get the current connection name + 'CLIENT SETNAME', // connection-name Set the current connection name + 'CONFIG GET', // parameter Get the value of a configuration parameter + 'CONFIG SET', // parameter value Set a configuration parameter to the given value + 'CONFIG RESETSTAT', // Reset the stats returned by INFO + 'DBSIZE', // Return the number of keys in the selected database + 'DEBUG OBJECT', // key Get debugging information about a key + 'DEBUG SEGFAULT', // Make the server crash + 'DECR', // key Decrement the integer value of a key by one + 'DECRBY', // key decrement Decrement the integer value of a key by the given number + 'DEL', // key [key ...] Delete a key + 'DISCARD', // Discard all commands issued after MULTI + 'DUMP', // key Return a serialized version of the value stored at the specified key. + 'ECHO', // message Echo the given string + 'EVAL', // script numkeys key [key ...] arg [arg ...] Execute a Lua script server side + 'EVALSHA', // sha1 numkeys key [key ...] arg [arg ...] Execute a Lua script server side + 'EXEC', // Execute all commands issued after MULTI + 'EXISTS', // key Determine if a key exists + 'EXPIRE', // key seconds Set a key's time to live in seconds + 'EXPIREAT', // key timestamp Set the expiration for a key as a UNIX timestamp + 'FLUSHALL', // Remove all keys from all databases + 'FLUSHDB', // Remove all keys from the current database + 'GET', // key Get the value of a key + 'GETBIT', // key offset Returns the bit value at offset in the string value stored at key + 'GETRANGE', // key start end Get a substring of the string stored at a key + 'GETSET', // key value Set the string value of a key and return its old value + 'HDEL', // key field [field ...] Delete one or more hash fields + 'HEXISTS', // key field Determine if a hash field exists + 'HGET', // key field Get the value of a hash field + 'HGETALL', // key Get all the fields and values in a hash + 'HINCRBY', // key field increment Increment the integer value of a hash field by the given number + 'HINCRBYFLOAT', // key field increment Increment the float value of a hash field by the given amount + 'HKEYS', // key Get all the fields in a hash + 'HLEN', // key Get the number of fields in a hash + 'HMGET', // key field [field ...] Get the values of all the given hash fields + 'HMSET', // key field value [field value ...] Set multiple hash fields to multiple values + 'HSET', // key field value Set the string value of a hash field + 'HSETNX', // key field value Set the value of a hash field, only if the field does not exist + 'HVALS', // key Get all the values in a hash + 'INCR', // key Increment the integer value of a key by one + 'INCRBY', // key increment Increment the integer value of a key by the given amount + 'INCRBYFLOAT', // key increment Increment the float value of a key by the given amount + 'INFO', // [section] Get information and statistics about the server + 'KEYS', // pattern Find all keys matching the given pattern + 'LASTSAVE', // Get the UNIX time stamp of the last successful save to disk + 'LINDEX', // key index Get an element from a list by its index + 'LINSERT', // key BEFORE|AFTER pivot value Insert an element before or after another element in a list + 'LLEN', // key Get the length of a list + 'LPOP', // key Remove and get the first element in a list + 'LPUSH', // key value [value ...] Prepend one or multiple values to a list + 'LPUSHX', // key value Prepend a value to a list, only if the list exists + 'LRANGE', // key start stop Get a range of elements from a list + 'LREM', // key count value Remove elements from a list + 'LSET', // key index value Set the value of an element in a list by its index + 'LTRIM', // key start stop Trim a list to the specified range + 'MGET', // key [key ...] Get the values of all the given keys + 'MIGRATE', // host port key destination-db timeout Atomically transfer a key from a Redis instance to another one. + 'MONITOR', // Listen for all requests received by the server in real time + 'MOVE', // key db Move a key to another database + 'MSET', // key value [key value ...] Set multiple keys to multiple values + 'MSETNX', // key value [key value ...] Set multiple keys to multiple values, only if none of the keys exist + 'MULTI', // Mark the start of a transaction block + 'OBJECT', // subcommand [arguments [arguments ...]] Inspect the internals of Redis objects + 'PERSIST', // key Remove the expiration from a key + 'PEXPIRE', // key milliseconds Set a key's time to live in milliseconds + 'PEXPIREAT', // key milliseconds-timestamp Set the expiration for a key as a UNIX timestamp specified in milliseconds + 'PING', // Ping the server + 'PSETEX', // key milliseconds value Set the value and expiration in milliseconds of a key + 'PSUBSCRIBE', // pattern [pattern ...] Listen for messages published to channels matching the given patterns + 'PTTL', // key Get the time to live for a key in milliseconds + 'PUBLISH', // channel message Post a message to a channel + 'PUNSUBSCRIBE', // [pattern [pattern ...]] Stop listening for messages posted to channels matching the given patterns + 'QUIT', // Close the connection + 'RANDOMKEY', // Return a random key from the keyspace + 'RENAME', // key newkey Rename a key + 'RENAMENX', // key newkey Rename a key, only if the new key does not exist + 'RESTORE', // key ttl serialized-value Create a key using the provided serialized value, previously obtained using DUMP. + 'RPOP', // key Remove and get the last element in a list + 'RPOPLPUSH', // source destination Remove the last element in a list, append it to another list and return it + 'RPUSH', // key value [value ...] Append one or multiple values to a list + 'RPUSHX', // key value Append a value to a list, only if the list exists + 'SADD', // key member [member ...] Add one or more members to a set + 'SAVE', // Synchronously save the dataset to disk + 'SCARD', // key Get the number of members in a set + 'SCRIPT EXISTS', // script [script ...] Check existence of scripts in the script cache. + 'SCRIPT FLUSH', // Remove all the scripts from the script cache. + 'SCRIPT KILL', // Kill the script currently in execution. + 'SCRIPT LOAD', // script Load the specified Lua script into the script cache. + 'SDIFF', // key [key ...] Subtract multiple sets + 'SDIFFSTORE', // destination key [key ...] Subtract multiple sets and store the resulting set in a key + 'SELECT', // index Change the selected database for the current connection + 'SET', // key value Set the string value of a key + 'SETBIT', // key offset value Sets or clears the bit at offset in the string value stored at key + 'SETEX', // key seconds value Set the value and expiration of a key + 'SETNX', // key value Set the value of a key, only if the key does not exist + 'SETRANGE', // key offset value Overwrite part of a string at key starting at the specified offset + 'SHUTDOWN', // [NOSAVE] [SAVE] Synchronously save the dataset to disk and then shut down the server + 'SINTER', // key [key ...] Intersect multiple sets + 'SINTERSTORE', // destination key [key ...] Intersect multiple sets and store the resulting set in a key + 'SISMEMBER', // key member Determine if a given value is a member of a set + 'SLAVEOF', // host port Make the server a slave of another instance, or promote it as master + 'SLOWLOG', // subcommand [argument] Manages the Redis slow queries log + 'SMEMBERS', // key Get all the members in a set + 'SMOVE', // source destination member Move a member from one set to another + 'SORT', // key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination] Sort the elements in a list, set or sorted set + 'SPOP', // key Remove and return a random member from a set + 'SRANDMEMBER', // key [count] Get one or multiple random members from a set + 'SREM', // key member [member ...] Remove one or more members from a set + 'STRLEN', // key Get the length of the value stored in a key + 'SUBSCRIBE', // channel [channel ...] Listen for messages published to the given channels + 'SUNION', // key [key ...] Add multiple sets + 'SUNIONSTORE', // destination key [key ...] Add multiple sets and store the resulting set in a key + 'SYNC', // Internal command used for replication + 'TIME', // Return the current server time + 'TTL', // key Get the time to live for a key + 'TYPE', // key Determine the type stored at key + 'UNSUBSCRIBE', // [channel [channel ...]] Stop listening for messages posted to the given channels + 'UNWATCH', // Forget about all watched keys + 'WATCH', // key [key ...] Watch the given keys to determine execution of the MULTI/EXEC block + 'ZADD', // key score member [score member ...] Add one or more members to a sorted set, or update its score if it already exists + 'ZCARD', // key Get the number of members in a sorted set + 'ZCOUNT', // key min max Count the members in a sorted set with scores within the given values + 'ZINCRBY', // key increment member Increment the score of a member in a sorted set + 'ZINTERSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Intersect multiple sorted sets and store the resulting sorted set in a new key + 'ZRANGE', // key start stop [WITHSCORES] Return a range of members in a sorted set, by index + 'ZRANGEBYSCORE', // key min max [WITHSCORES] [LIMIT offset count] Return a range of members in a sorted set, by score + 'ZRANK', // key member Determine the index of a member in a sorted set + 'ZREM', // key member [member ...] Remove one or more members from a sorted set + 'ZREMRANGEBYRANK', // key start stop Remove all members in a sorted set within the given indexes + 'ZREMRANGEBYSCORE', // key min max Remove all members in a sorted set within the given scores + 'ZREVRANGE', // key start stop [WITHSCORES] Return a range of members in a sorted set, by index, with scores ordered from high to low + 'ZREVRANGEBYSCORE', // key max min [WITHSCORES] [LIMIT offset count] Return a range of members in a sorted set, by score, with scores ordered from high to low + 'ZREVRANK', // key member Determine the index of a member in a sorted set, with scores ordered from high to low + 'ZSCORE', // key member Get the score associated with the given member in a sorted set + 'ZUNIONSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Add multiple sorted sets and store the resulting sorted set in a new key + ); /** * @var Transaction the currently active transaction */ private $_transaction; + /** + * @var resource redis socket connection + */ + private $_socket; /** * Closes the connection when this component is being serialized. @@ -85,7 +228,7 @@ class Connection extends Component */ public function getIsActive() { - return $this->redis !== null; + return $this->_socket !== null; } /** @@ -95,7 +238,7 @@ class Connection extends Component */ public function open() { - if ($this->redis === null) { + if ($this->_socket === null) { if (empty($this->dsn)) { throw new InvalidConfigException('Connection.dsn cannot be empty.'); } @@ -104,8 +247,9 @@ class Connection extends Component $port = 6379; try { \Yii::trace('Opening DB connection: ' . $this->dsn, __CLASS__); - // TODO connection to redis seems to be very easy, consider writing own connect - $this->redis = new \Jamm\Memory\RedisServer($host, $port); + $this->_socket = stream_socket_client($host . ':' . $port); + // TODO auth + // TODO select database $this->initConnection(); } catch (\PDOException $e) { @@ -122,9 +266,11 @@ class Connection extends Component */ public function close() { - if ($this->redis !== null) { + if ($this->_socket !== null) { \Yii::trace('Closing DB connection: ' . $this->dsn, __CLASS__); - $this->redis = null; + // TODO send CLOSE to the server + stream_socket_shutdown($this->_socket, STREAM_SHUT_RDWR); + $this->_socket = null; $this->_transaction = null; } } @@ -141,23 +287,6 @@ class Connection extends Component $this->trigger(self::EVENT_AFTER_OPEN); } - - /** - * Creates a command for execution. - * @param string $query the SQL statement to be executed - * @param array $params the parameters to be bound to the SQL statement - * @return Command the DB command - */ - public function createCommand($query = null, $params = array()) - { - $this->open(); - $command = new Command(array( - 'db' => $this, - 'query' => $query, - )); - return $command->addValues($params); - } - /** * Returns the currently active transaction. * @return Transaction the currently active transaction. Null if no active transaction. @@ -182,37 +311,6 @@ class Connection extends Component } /** - * Returns the schema information for the database opened by this connection. - * @return Schema the schema information for the database opened by this connection. - * @throws NotSupportedException if there is no support for the current driver type - */ - public function getSchema() - { - $driver = $this->getDriverName(); - throw new NotSupportedException("Connection does not support reading schema information for '$driver' DBMS."); - } - - /** - * Returns the query builder for the current DB connection. - * @return QueryBuilder the query builder for the current DB connection. - */ - public function getQueryBuilder() - { - return $this->getSchema()->getQueryBuilder(); - } - - /** - * Obtains the schema information for the named table. - * @param string $name table name. - * @param boolean $refresh whether to reload the table schema even if it is found in the cache. - * @return TableSchema table schema information. Null if the named table does not exist. - */ - public function getTableSchema($name, $refresh = false) - { - return $this->getSchema()->getTableSchema($name, $refresh); - } - - /** * Returns the name of the DB driver for the current [[dsn]]. * @return string name of the DB driver */ @@ -225,6 +323,23 @@ class Connection extends Component } } + public function __call($name, $params) + { + if (in_array($name, $this->redisCommands)) + { + array_unshift($params, $name); + $cmd = '*' . count($params) . "\r\n"; + foreach($params as $arg) { + $cmd .= '$' . strlen( $item ) . "\r\n" . $item . "\r\n"; + } + fwrite( $this->_socket, $cmd ); + return $this->_parseResponse(); + } + else { + return parent::__call($name, $params); + } + } + /** * Returns the statistical results of SQL queries. * The results returned include the number of SQL statements executed and @@ -235,7 +350,7 @@ class Connection extends Component * @see \yii\logging\Logger::getProfiling() */ public function getQuerySummary() - { + {// TODO implement $logger = \Yii::getLogger(); $timings = $logger->getProfiling(array('yii\db\Command::query', 'yii\db\Command::execute')); $count = count($timings);