Browse Source

Redis concept finished except relations

tags/2.0.0-beta
Carsten Brandt 12 years ago
parent
commit
39a1ce406b
  1. 2
      framework/db/ActiveRelation.php
  2. 288
      framework/db/redis/ActiveQuery.php
  3. 14
      framework/db/redis/ActiveRecord.php
  4. 2
      framework/db/redis/ActiveRelation.php
  5. 567
      framework/db/redis/Command.php
  6. 237
      framework/db/redis/Connection.php

2
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. * @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 * 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. * 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; public $link;
/** /**

288
framework/db/redis/ActiveQuery.php

@ -11,20 +11,82 @@
namespace yii\db\redis; 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 <mail@cebe.cc> * @author Carsten Brandt <mail@cebe.cc>
* @since 2.0 * @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. * 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. * @return array the query results. If the query results in nothing, an empty array will be returned.
*/ */
public function all() public function all()
{ {
// TODO implement
$command = $this->createCommand(); $command = $this->createCommand();
$rows = $command->queryAll(); $rows = $command->queryAll();
if ($rows !== array()) { if ($rows !== array()) {
@ -46,6 +108,7 @@ class ActiveQuery extends \yii\db\ActiveQuery
*/ */
public function one() public function one()
{ {
// TODO implement
$command = $this->createCommand(); $command = $this->createCommand();
$row = $command->queryRow(); $row = $command->queryRow();
if ($row !== false && !$this->asArray) { if ($row !== false && !$this->asArray) {
@ -71,6 +134,7 @@ class ActiveQuery extends \yii\db\ActiveQuery
*/ */
public function count($q = '*') public function count($q = '*')
{ {
// TODO implement
$this->select = array("COUNT($q)"); $this->select = array("COUNT($q)");
return $this->createCommand()->queryScalar(); return $this->createCommand()->queryScalar();
} }
@ -83,6 +147,7 @@ class ActiveQuery extends \yii\db\ActiveQuery
*/ */
public function sum($q) public function sum($q)
{ {
// TODO implement
$this->select = array("SUM($q)"); $this->select = array("SUM($q)");
return $this->createCommand()->queryScalar(); return $this->createCommand()->queryScalar();
} }
@ -95,6 +160,7 @@ class ActiveQuery extends \yii\db\ActiveQuery
*/ */
public function average($q) public function average($q)
{ {
// TODO implement
$this->select = array("AVG($q)"); $this->select = array("AVG($q)");
return $this->createCommand()->queryScalar(); return $this->createCommand()->queryScalar();
} }
@ -107,6 +173,7 @@ class ActiveQuery extends \yii\db\ActiveQuery
*/ */
public function min($q) public function min($q)
{ {
// TODO implement
$this->select = array("MIN($q)"); $this->select = array("MIN($q)");
return $this->createCommand()->queryScalar(); return $this->createCommand()->queryScalar();
} }
@ -119,6 +186,7 @@ class ActiveQuery extends \yii\db\ActiveQuery
*/ */
public function max($q) public function max($q)
{ {
// TODO implement
$this->select = array("MAX($q)"); $this->select = array("MAX($q)");
return $this->createCommand()->queryScalar(); return $this->createCommand()->queryScalar();
} }
@ -131,6 +199,7 @@ class ActiveQuery extends \yii\db\ActiveQuery
*/ */
public function scalar() public function scalar()
{ {
// TODO implement
return $this->createCommand()->queryScalar(); return $this->createCommand()->queryScalar();
} }
@ -140,33 +209,214 @@ class ActiveQuery extends \yii\db\ActiveQuery
*/ */
public function exists() public function exists()
{ {
// TODO implement
$this->select = array(new Expression('1')); $this->select = array(new Expression('1'));
return $this->scalar() !== false; 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;
}
/**
* 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 addOrderBy($columns)
{
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);
}
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;
}
/** /**
* Creates a DB command that can be used to execute this query. * Specifies the relations with which this query should be performed.
* @param Connection $db the DB connection used to create the DB command. *
* If null, the DB connection returned by [[modelClass]] will be used. * The parameters to this method can be either one or multiple strings, or a single array
* @return Command the created DB command instance. * 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 createCommand($db = null) 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;
}
} 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)
{ {
/** @var $modelClass ActiveRecord */ $primaryModel = new $this->modelClass;
$modelClass = $this->modelClass; $relations = $this->normalizeRelations($primaryModel, $with);
if ($db === null) { foreach ($relations as $name => $relation) {
$db = $modelClass::getDb(); if ($relation->asArray === null) {
// inherit asArray from primary query
$relation->asArray = $this->asArray;
} }
if ($this->sql === null) { $relation->findWith($name, $models);
if ($this->from === null) {
$tableName = $modelClass::tableName();
$this->from = array($tableName);
} }
/** @var $qb QueryBuilder */
$qb = $db->getQueryBuilder();
$this->sql = $qb->build($this);
} }
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;
}
} }

14
framework/db/redis/ActiveRecord.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.
* *
@ -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. * The default implementation will return all column names of the table associated with this AR class.
* @return array list of attribute names. * @return array list of attribute names.
*/ */
public function attributes() public function attributes() // TODO: refactor should be abstract in an ActiveRecord base class
{ {
return array(); return array();
} }
@ -93,9 +95,7 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord
*/ */
public static function findBySql($sql, $params = array()) public static function findBySql($sql, $params = array())
{ {
$query = static::createQuery(); throw new NotSupportedException('findBySql() is not supported by redis ActiveRecord');
$query->sql = $sql;
return $query->params($params);
} }
@ -121,7 +121,7 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord
*/ */
public static function getTableSchema() 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. * @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();
} }
} }

2
framework/db/redis/ActiveRelation.php

@ -19,5 +19,5 @@ namespace yii\db\redis;
*/ */
class ActiveRelation extends \yii\db\ActiveRelation class ActiveRelation extends \yii\db\ActiveRelation
{ {
// TODO implement
} }

567
framework/db/redis/Command.php

@ -1,567 +0,0 @@
<?php
/**
* Command class file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\redis;
use yii\base\NotSupportedException;
use yii\db\Exception;
// TODO ensure proper value quoting against "SQL"injection
/**
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @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);
}
}

237
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. * @var string the password for establishing DB connection. Defaults to empty string.
*/ */
public $password = ''; public $password = '';
/**
* @var \Jamm\Memory\RedisServer
*/
public $redis;
/** /**
* @var boolean whether to enable profiling for the SQL statements being executed. * @var boolean whether to enable profiling for the SQL statements being executed.
* Defaults to false. This should be mainly enabled and used during development * Defaults to false. This should be mainly enabled and used during development
@ -64,10 +58,159 @@ class Connection extends Component
* @see enableAutoQuoting * @see enableAutoQuoting
*/ */
public $keyPrefix; 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 * @var Transaction the currently active transaction
*/ */
private $_transaction; private $_transaction;
/**
* @var resource redis socket connection
*/
private $_socket;
/** /**
* Closes the connection when this component is being serialized. * Closes the connection when this component is being serialized.
@ -85,7 +228,7 @@ class Connection extends Component
*/ */
public function getIsActive() public function getIsActive()
{ {
return $this->redis !== null; return $this->_socket !== null;
} }
/** /**
@ -95,7 +238,7 @@ class Connection extends Component
*/ */
public function open() public function open()
{ {
if ($this->redis === null) { if ($this->_socket === null) {
if (empty($this->dsn)) { if (empty($this->dsn)) {
throw new InvalidConfigException('Connection.dsn cannot be empty.'); throw new InvalidConfigException('Connection.dsn cannot be empty.');
} }
@ -104,8 +247,9 @@ class Connection extends Component
$port = 6379; $port = 6379;
try { try {
\Yii::trace('Opening DB connection: ' . $this->dsn, __CLASS__); \Yii::trace('Opening DB connection: ' . $this->dsn, __CLASS__);
// TODO connection to redis seems to be very easy, consider writing own connect $this->_socket = stream_socket_client($host . ':' . $port);
$this->redis = new \Jamm\Memory\RedisServer($host, $port); // TODO auth
// TODO select database
$this->initConnection(); $this->initConnection();
} }
catch (\PDOException $e) { catch (\PDOException $e) {
@ -122,9 +266,11 @@ class Connection extends Component
*/ */
public function close() public function close()
{ {
if ($this->redis !== null) { if ($this->_socket !== null) {
\Yii::trace('Closing DB connection: ' . $this->dsn, __CLASS__); \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; $this->_transaction = null;
} }
} }
@ -141,23 +287,6 @@ class Connection extends Component
$this->trigger(self::EVENT_AFTER_OPEN); $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. * Returns the currently active transaction.
* @return Transaction the currently active transaction. Null if no 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]]. * Returns the name of the DB driver for the current [[dsn]].
* @return string name of the DB driver * @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. * Returns the statistical results of SQL queries.
* The results returned include the number of SQL statements executed and * The results returned include the number of SQL statements executed and
@ -235,7 +350,7 @@ class Connection extends Component
* @see \yii\logging\Logger::getProfiling() * @see \yii\logging\Logger::getProfiling()
*/ */
public function getQuerySummary() public function getQuerySummary()
{ {// TODO implement
$logger = \Yii::getLogger(); $logger = \Yii::getLogger();
$timings = $logger->getProfiling(array('yii\db\Command::query', 'yii\db\Command::execute')); $timings = $logger->getProfiling(array('yii\db\Command::query', 'yii\db\Command::execute'));
$count = count($timings); $count = count($timings);

Loading…
Cancel
Save