Browse Source

w

tags/2.0.0-beta
Qiang Xue 14 years ago
parent
commit
05c7915d8a
  1. 148
      framework/db/dao/ColumnSchema.php
  2. 4
      framework/db/dao/Command.php
  3. 61
      framework/db/dao/Expression.php
  4. 520
      framework/db/dao/Query.php
  5. 402
      framework/db/dao/QueryBuilder.php
  6. 556
      framework/db/dao/Schema.php
  7. 76
      framework/db/dao/TableSchema.php

148
framework/db/dao/ColumnSchema.php

@ -0,0 +1,148 @@
<?php
/**
* CDbColumnSchema class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CDbColumnSchema class describes the column meta data of a database table.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CDbColumnSchema.php 3099 2011-03-19 01:26:47Z qiang.xue $
* @package system.db.schema
* @since 1.0
*/
class CDbColumnSchema extends CComponent
{
/**
* @var string name of this column (without quotes).
*/
public $name;
/**
* @var string raw name of this column. This is the quoted name that can be used in SQL queries.
*/
public $rawName;
/**
* @var boolean whether this column can be null.
*/
public $allowNull;
/**
* @var string the DB type of this column.
*/
public $dbType;
/**
* @var string the PHP type of this column.
*/
public $type;
/**
* @var mixed default value of this column
*/
public $defaultValue;
/**
* @var integer size of the column.
*/
public $size;
/**
* @var integer precision of the column data, if it is numeric.
*/
public $precision;
/**
* @var integer scale of the column data, if it is numeric.
*/
public $scale;
/**
* @var boolean whether this column is a primary key
*/
public $isPrimaryKey;
/**
* @var boolean whether this column is a foreign key
*/
public $isForeignKey;
/**
* @var boolean whether this column is auto-incremental
* @since 1.1.7
*/
public $autoIncrement = false;
/**
* Initializes the column with its DB type and default value.
* This sets up the column's PHP type, size, precision, scale as well as default value.
* @param string $dbType the column's DB type
* @param mixed $defaultValue the default value
*/
public function init($dbType, $defaultValue)
{
$this->dbType = $dbType;
$this->extractType($dbType);
$this->extractLimit($dbType);
if ($defaultValue !== null)
$this->extractDefault($defaultValue);
}
/**
* Extracts the PHP type from DB type.
* @param string $dbType DB type
*/
protected function extractType($dbType)
{
if (stripos($dbType, 'int') !== false && stripos($dbType, 'unsigned int') === false)
$this->type = 'integer';
elseif (stripos($dbType, 'bool') !== false)
$this->type = 'boolean';
elseif (preg_match('/(real|floa|doub)/i', $dbType))
$this->type = 'double';
else
$this->type = 'string';
}
/**
* Extracts size, precision and scale information from column's DB type.
* @param string $dbType the column's DB type
*/
protected function extractLimit($dbType)
{
if (strpos($dbType, '(') && preg_match('/\((.*)\)/', $dbType, $matches))
{
$values = explode(',', $matches[1]);
$this->size = $this->precision = (int)$values[0];
if (isset($values[1]))
$this->scale = (int)$values[1];
}
}
/**
* Extracts the default value for the column.
* The value is typecasted to correct PHP type.
* @param mixed $defaultValue the default value obtained from metadata
*/
protected function extractDefault($defaultValue)
{
$this->defaultValue = $this->typecast($defaultValue);
}
/**
* Converts the input value to the type that this column is of.
* @param mixed $value input value
* @return mixed converted value
*/
public function typecast($value)
{
if (gettype($value) === $this->type || $value === null || $value instanceof CDbExpression)
return $value;
if ($value === '')
return $this->type === 'string' ? '' : null;
switch ($this->type)
{
case 'string': return (string)$value;
case 'integer': return (integer)$value;
case 'boolean': return (boolean)$value;
case 'double':
default: return $value;
}
}
}

4
framework/db/dao/Command.php

@ -8,6 +8,8 @@
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\dao;
/**
* Command represents a SQL statement to be executed against a database.
*
@ -40,7 +42,7 @@
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Command extends CComponent
class Command extends \yii\base\Component
{
/**
* @var array the parameters (name=>value) to be bound to the current query.

61
framework/db/dao/Expression.php

@ -0,0 +1,61 @@
<?php
/**
* Expression class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\dao;
/**
* Expression represents a DB expression that does not need escaping or quoting.
* When an Expression object is embedded within a SQL statement or fragment,
* it will be replaced with the [[expression]] property value without any
* DB escaping or quoting. For example,
*
* ~~~
* $expression = new Expression('NOW()');
* $sql = 'SELECT ' . $expression; // SELECT NOW()
* ~~~
*
* An expression can also be bound with parameters specified via [[params]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Expression
{
/**
* @var string the DB expression
*/
public $expression;
/**
* @var array list of parameters that should be bound for this expression.
* The keys are placeholders appearing in [[expression]] and the values
* are the corresponding parameter values.
*/
public $params = array();
/**
* Constructor.
* @param string $expression the DB expression
* @param array $params parameters
*/
public function __construct($expression, $params = array())
{
$this->expression = $expression;
$this->params = $params;
}
/**
* String magic method
* @return string the DB expression
*/
public function __toString()
{
return $this->expression;
}
}

520
framework/db/dao/Query.php

@ -0,0 +1,520 @@
<?php
/**
* Query class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\dao;
/**
* Query represents the components in a DB query.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Query extends CComponent
{
/**
* @var mixed the columns being selected. This refers to the SELECT clause in an SQL
* statement. The property can be either a string (column names separated by commas)
* or an array of column names. Defaults to '*', meaning all columns.
*/
public $select;
public $from;
/**
* @var boolean whether to select distinct rows of data only. If this is set true,
* the SELECT clause would be changed to SELECT DISTINCT.
*/
public $distinct;
/**
* @var string query condition. This refers to the WHERE clause in an SQL statement.
* For example, <code>age>31 AND team=1</code>.
*/
public $where;
/**
* @var integer maximum number of records to be returned. If less than 0, it means no limit.
*/
public $limit;
/**
* @var integer zero-based offset from where the records are to be returned. If less than 0, it means starting from the beginning.
*/
public $offset;
/**
* @var string how to sort the query results. This refers to the ORDER BY clause in an SQL statement.
*/
public $orderBy;
/**
* @var string how to group the query results. This refers to the GROUP BY clause in an SQL statement.
* For example, <code>'projectID, teamID'</code>.
*/
public $groupBy;
/**
* @var string how to join with other tables. This refers to the JOIN clause in an SQL statement.
* For example, <code>'LEFT JOIN users ON users.id=authorID'</code>.
*/
public $join;
/**
* @var string the condition to be applied with GROUP-BY clause.
* For example, <code>'SUM(revenue)<50000'</code>.
*/
public $having;
/**
* @var array list of query parameter values indexed by parameter placeholders.
* For example, <code>array(':name'=>'Dan', ':age'=>31)</code>.
*/
public $params;
public $union;
public function getSql($connection)
{
return $connection->getQueryBuilder()->build($this);
}
/**
* Appends a condition to the existing {@link condition}.
* The new condition and the existing condition will be concatenated via the specified operator
* which defaults to 'AND'.
* The new condition can also be an array. In this case, all elements in the array
* will be concatenated together via the operator.
* This method handles the case when the existing condition is empty.
* After calling this method, the {@link condition} property will be modified.
* @param mixed $condition the new condition. It can be either a string or an array of strings.
* @param string $operator the operator to join different conditions. Defaults to 'AND'.
* @return Query the criteria object itself
* @since 1.0.9
*/
public function addCondition($condition, $operator = 'AND')
{
if (is_array($condition))
{
if ($condition === array())
return $this;
$condition = '(' . implode(') ' . $operator . ' (', $condition) . ')';
}
if ($this->condition === '')
$this->condition = $condition;
else
$this->condition = '(' . $this->condition . ') ' . $operator . ' (' . $condition . ')';
return $this;
}
/**
* Appends a search condition to the existing {@link condition}.
* The search condition and the existing condition will be concatenated via the specified operator
* which defaults to 'AND'.
* The search condition is generated using the SQL LIKE operator with the given column name and
* search keyword.
* @param string $column the column name (or a valid SQL expression)
* @param string $keyword the search keyword. This interpretation of the keyword is affected by the next parameter.
* @param boolean $escape whether the keyword should be escaped if it contains characters % or _.
* When this parameter is true (default), the special characters % (matches 0 or more characters)
* and _ (matches a single character) will be escaped, and the keyword will be surrounded with a %
* character on both ends. When this parameter is false, the keyword will be directly used for
* matching without any change.
* @param string $operator the operator used to concatenate the new condition with the existing one.
* Defaults to 'AND'.
* @param string $like the LIKE operator. Defaults to 'LIKE'. You may also set this to be 'NOT LIKE'.
* @return Query the criteria object itself
* @since 1.0.10
*/
public function addSearchCondition($column, $keyword, $escape = true, $operator = 'AND', $like = 'LIKE')
{
if ($keyword == '')
return $this;
if ($escape)
$keyword = '%' . strtr($keyword, array('%' => '\%', '_' => '\_', '\\' => '\\\\')) . '%';
$condition = $column . " $like " . self::PARAM_PREFIX . self::$paramCount;
$this->params[self::PARAM_PREFIX . self::$paramCount++] = $keyword;
return $this->addCondition($condition, $operator);
}
/**
* Appends an IN condition to the existing {@link condition}.
* The IN condition and the existing condition will be concatenated via the specified operator
* which defaults to 'AND'.
* The IN condition is generated by using the SQL IN operator which requires the specified
* column value to be among the given list of values.
* @param string $column the column name (or a valid SQL expression)
* @param array $values list of values that the column value should be in
* @param string $operator the operator used to concatenate the new condition with the existing one.
* Defaults to 'AND'.
* @return Query the criteria object itself
* @since 1.0.10
*/
public function addInCondition($column, $values, $operator = 'AND')
{
if (($n = count($values)) < 1)
return $this->addCondition('0=1', $operator); // 0=1 is used because in MSSQL value alone can't be used in WHERE
if ($n === 1)
{
$value = reset($values);
if ($value === null)
return $this->addCondition($column . ' IS NULL');
$condition = $column . '=' . self::PARAM_PREFIX . self::$paramCount;
$this->params[self::PARAM_PREFIX . self::$paramCount++] = $value;
}
else
{
$params = array();
foreach ($values as $value)
{
$params[] = self::PARAM_PREFIX . self::$paramCount;
$this->params[self::PARAM_PREFIX . self::$paramCount++] = $value;
}
$condition = $column . ' IN (' . implode(', ', $params) . ')';
}
return $this->addCondition($condition, $operator);
}
/**
* Appends an NOT IN condition to the existing {@link condition}.
* The NOT IN condition and the existing condition will be concatenated via the specified operator
* which defaults to 'AND'.
* The NOT IN condition is generated by using the SQL NOT IN operator which requires the specified
* column value to be among the given list of values.
* @param string $column the column name (or a valid SQL expression)
* @param array $values list of values that the column value should not be in
* @param string $operator the operator used to concatenate the new condition with the existing one.
* Defaults to 'AND'.
* @return Query the criteria object itself
* @since 1.1.1
*/
public function addNotInCondition($column, $values, $operator = 'AND')
{
if (($n = count($values)) < 1)
return $this;
if ($n === 1)
{
$value = reset($values);
if ($value === null)
return $this->addCondition($column . ' IS NOT NULL');
$condition = $column . '!=' . self::PARAM_PREFIX . self::$paramCount;
$this->params[self::PARAM_PREFIX . self::$paramCount++] = $value;
}
else
{
$params = array();
foreach ($values as $value)
{
$params[] = self::PARAM_PREFIX . self::$paramCount;
$this->params[self::PARAM_PREFIX . self::$paramCount++] = $value;
}
$condition = $column . ' NOT IN (' . implode(', ', $params) . ')';
}
return $this->addCondition($condition, $operator);
}
/**
* Appends a condition for matching the given list of column values.
* The generated condition will be concatenated to the existing {@link condition}
* via the specified operator which defaults to 'AND'.
* The condition is generated by matching each column and the corresponding value.
* @param array $columns list of column names and values to be matched (name=>value)
* @param string $columnOperator the operator to concatenate multiple column matching condition. Defaults to 'AND'.
* @param string $operator the operator used to concatenate the new condition with the existing one.
* Defaults to 'AND'.
* @return Query the criteria object itself
* @since 1.0.10
*/
public function addColumnCondition($columns, $columnOperator = 'AND', $operator = 'AND')
{
$params = array();
foreach ($columns as $name => $value)
{
if ($value === null)
$params[] = $name . ' IS NULL';
else
{
$params[] = $name . '=' . self::PARAM_PREFIX . self::$paramCount;
$this->params[self::PARAM_PREFIX . self::$paramCount++] = $value;
}
}
return $this->addCondition(implode(" $columnOperator ", $params), $operator);
}
/**
* Adds a comparison expression to the {@link condition} property.
*
* This method is a helper that appends to the {@link condition} property
* with a new comparison expression. The comparison is done by comparing a column
* with the given value using some comparison operator.
*
* The comparison operator is intelligently determined based on the first few
* characters in the given value. In particular, it recognizes the following operators
* if they appear as the leading characters in the given value:
* <ul>
* <li><code>&lt;</code>: the column must be less than the given value.</li>
* <li><code>&gt;</code>: the column must be greater than the given value.</li>
* <li><code>&lt;=</code>: the column must be less than or equal to the given value.</li>
* <li><code>&gt;=</code>: the column must be greater than or equal to the given value.</li>
* <li><code>&lt;&gt;</code>: the column must not be the same as the given value.
* Note that when $partialMatch is true, this would mean the value must not be a substring
* of the column.</li>
* <li><code>=</code>: the column must be equal to the given value.</li>
* <li>none of the above: the column must be equal to the given value. Note that when $partialMatch
* is true, this would mean the value must be the same as the given value or be a substring of it.</li>
* </ul>
*
* Note that any surrounding white spaces will be removed from the value before comparison.
* When the value is empty, no comparison expression will be added to the search condition.
*
* @param string $column the name of the column to be searched
* @param mixed $value the column value to be compared with. If the value is a string, the aforementioned
* intelligent comparison will be conducted. If the value is an array, the comparison is done
* by exact match of any of the value in the array. If the string or the array is empty,
* the existing search condition will not be modified.
* @param boolean $partialMatch whether the value should consider partial text match (using LIKE and NOT LIKE operators).
* Defaults to false, meaning exact comparison.
* @param string $operator the operator used to concatenate the new condition with the existing one.
* Defaults to 'AND'.
* @param boolean $escape whether the value should be escaped if $partialMatch is true and
* the value contains characters % or _. When this parameter is true (default),
* the special characters % (matches 0 or more characters)
* and _ (matches a single character) will be escaped, and the value will be surrounded with a %
* character on both ends. When this parameter is false, the value will be directly used for
* matching without any change.
* @return Query the criteria object itself
* @since 1.1.1
*/
public function compare($column, $value, $partialMatch = false, $operator = 'AND', $escape = true)
{
if (is_array($value))
{
if ($value === array())
return $this;
return $this->addInCondition($column, $value, $operator);
}
else
$value = "$value";
if (preg_match('/^(?:\s*(<>|<=|>=|<|>|=))?(.*)$/', $value, $matches))
{
$value = $matches[2];
$op = $matches[1];
}
else
$op = '';
if ($value === '')
return $this;
if ($partialMatch)
{
if ($op === '')
return $this->addSearchCondition($column, $value, $escape, $operator);
if ($op === '<>')
return $this->addSearchCondition($column, $value, $escape, $operator, 'NOT LIKE');
}
elseif ($op === '')
$op = '=';
$this->addCondition($column . $op . self::PARAM_PREFIX . self::$paramCount, $operator);
$this->params[self::PARAM_PREFIX . self::$paramCount++] = $value;
return $this;
}
/**
* Adds a between condition to the {@link condition} property.
*
* The new between condition and the existing condition will be concatenated via
* the specified operator which defaults to 'AND'.
* If one or both values are empty then the condition is not added to the existing condition.
* This method handles the case when the existing condition is empty.
* After calling this method, the {@link condition} property will be modified.
* @param string $column the name of the column to search between.
* @param string $valueStart the beginning value to start the between search.
* @param string $valueEnd the ending value to end the between search.
* @param string $operator the operator used to concatenate the new condition with the existing one.
* Defaults to 'AND'.
* @return Query the criteria object itself
* @since 1.1.2
*/
public function addBetweenCondition($column, $valueStart, $valueEnd, $operator = 'AND')
{
if ($valueStart === '' || $valueEnd === '')
return $this;
$paramStart = self::PARAM_PREFIX . self::$paramCount++;
$paramEnd = self::PARAM_PREFIX . self::$paramCount++;
$this->params[$paramStart] = $valueStart;
$this->params[$paramEnd] = $valueEnd;
$condition = "$column BETWEEN $paramStart AND $paramEnd";
if ($this->condition === '')
$this->condition = $condition;
else
$this->condition = '(' . $this->condition . ') ' . $operator . ' (' . $condition . ')';
return $this;
}
/**
* Merges with another criteria.
* In general, the merging makes the resulting criteria more restrictive.
* For example, if both criterias have conditions, they will be 'AND' together.
* Also, the criteria passed as the parameter takes precedence in case
* two options cannot be merged (e.g. LIMIT, OFFSET).
* @param Query $criteria the criteria to be merged with.
* @param boolean $useAnd whether to use 'AND' to merge condition and having options.
* If false, 'OR' will be used instead. Defaults to 'AND'. This parameter has been
* available since version 1.0.6.
* @since 1.0.5
*/
public function mergeWith($criteria, $useAnd = true)
{
$and = $useAnd ? 'AND' : 'OR';
if (is_array($criteria))
$criteria = new self($criteria);
if ($this->select !== $criteria->select)
{
if ($this->select === '*')
$this->select = $criteria->select;
elseif ($criteria->select !== '*')
{
$select1 = is_string($this->select) ? preg_split('/\s*,\s*/', trim($this->select), -1, PREG_SPLIT_NO_EMPTY) : $this->select;
$select2 = is_string($criteria->select) ? preg_split('/\s*,\s*/', trim($criteria->select), -1, PREG_SPLIT_NO_EMPTY) : $criteria->select;
$this->select = array_merge($select1, array_diff($select2, $select1));
}
}
if ($this->condition !== $criteria->condition)
{
if ($this->condition === '')
$this->condition = $criteria->condition;
elseif ($criteria->condition !== '')
$this->condition = "( {$this->condition}) $and ( {$criteria->condition})";
}
if ($this->params !== $criteria->params)
$this->params = array_merge($this->params, $criteria->params);
if ($criteria->limit > 0)
$this->limit = $criteria->limit;
if ($criteria->offset >= 0)
$this->offset = $criteria->offset;
if ($criteria->alias !== null)
$this->alias = $criteria->alias;
if ($this->order !== $criteria->order)
{
if ($this->order === '')
$this->order = $criteria->order;
elseif ($criteria->order !== '')
$this->order = $criteria->order . ', ' . $this->order;
}
if ($this->group !== $criteria->group)
{
if ($this->group === '')
$this->group = $criteria->group;
elseif ($criteria->group !== '')
$this->group .= ', ' . $criteria->group;
}
if ($this->join !== $criteria->join)
{
if ($this->join === '')
$this->join = $criteria->join;
elseif ($criteria->join !== '')
$this->join .= ' ' . $criteria->join;
}
if ($this->having !== $criteria->having)
{
if ($this->having === '')
$this->having = $criteria->having;
elseif ($criteria->having !== '')
$this->having = "( {$this->having}) $and ( {$criteria->having})";
}
if ($criteria->distinct > 0)
$this->distinct = $criteria->distinct;
if ($criteria->together !== null)
$this->together = $criteria->together;
if ($criteria->index !== null)
$this->index = $criteria->index;
if (empty($this->scopes))
$this->scopes = $criteria->scopes;
elseif (!empty($criteria->scopes))
{
$scopes1 = (array)$this->scopes;
$scopes2 = (array)$criteria->scopes;
foreach ($scopes1 as $k => $v)
{
if (is_integer($k))
$scopes[] = $v;
elseif (isset($scopes2[$k]))
$scopes[] = array($k => $v);
else
$scopes[$k] = $v;
}
foreach ($scopes2 as $k => $v)
{
if (is_integer($k))
$scopes[] = $v;
elseif (isset($scopes1[$k]))
$scopes[] = array($k => $v);
else
$scopes[$k] = $v;
}
$this->scopes = $scopes;
}
if (empty($this->with))
$this->with = $criteria->with;
elseif (!empty($criteria->with))
{
$this->with = (array)$this->with;
foreach ((array)$criteria->with as $k => $v)
{
if (is_integer($k))
$this->with[] = $v;
elseif (isset($this->with[$k]))
{
$excludes = array();
foreach (array('joinType', 'on') as $opt)
{
if (isset($this->with[$k][$opt]))
$excludes[$opt] = $this->with[$k][$opt];
if (isset($v[$opt]))
$excludes[$opt] = ($opt === 'on' && isset($excludes[$opt]) && $v[$opt] !== $excludes[$opt]) ?
"($excludes[$opt]) AND $v[$opt]" : $v[$opt];
unset($this->with[$k][$opt]);
unset($v[$opt]);
}
$this->with[$k] = new self($this->with[$k]);
$this->with[$k]->mergeWith($v, $useAnd);
$this->with[$k] = $this->with[$k]->toArray();
if (count($excludes) !== 0)
$this->with[$k] = CMap::mergeArray($this->with[$k], $excludes);
}
else
$this->with[$k] = $v;
}
}
}
/**
* @return array the array representation of the criteria
* @since 1.0.6
*/
public function toArray()
{
$result = array();
foreach (array('select', 'condition', 'params', 'limit', 'offset', 'order', 'group', 'join', 'having', 'distinct', 'scopes', 'with', 'alias', 'index', 'together') as $name)
$result[$name] = $this->$name;
return $result;
}
}

402
framework/db/dao/QueryBuilder.php

@ -0,0 +1,402 @@
<?php
/**
* This file contains the Command class.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\dao;
/**
* QueryBuilder builds a SQL statement based on the specification given as a [[Query]] object.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class QueryBuilder extends \yii\base\Component
{
private $_connection;
public function __construct(Connection $connection)
{
$this->_connection = $connection;
}
/**
* @return CDbConnection the connection associated with this command
*/
public function getConnection()
{
return $this->_connection;
}
public function build($query)
{
$clauses = array(
$this->buildSelect($query->select, $query->distinct),
$this->buildFrom($query->from),
$this->buildJoin($query->join),
$this->buildWhere($query->where),
$this->buildGroupBy($query->groupBy),
$this->buildHaving($query->having),
$this->buildOrderBy($query->orderBy),
$this->buildLimit($query->offset, $query->limit),
$this->buildUnion($query->union),
);
return implode("\n", array_filter($clauses));
}
protected function buildSelect($columns, $distinct)
{
$select = $distinct ? 'SELECT DISTINCT' : 'SELECT';
if (empty($columns)) {
return $select . ' *';
}
if (is_string($columns)) {
if (strpos($columns, '(') !== false) {
return $select . ' ' . $columns;
}
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
}
foreach ($columns as $i => $column) {
if (is_object($column)) {
$columns[$i] = (string)$column;
}
elseif (strpos($column, '(') === false) {
if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $column, $matches)) {
$columns[$i] = $this->_connection->quoteColumnName($matches[1]) . ' AS ' . $this->_connection->quoteColumnName($matches[2]);
}
else {
$columns[$i] = $this->_connection->quoteColumnName($column);
}
}
}
return $select . ' ' . implode(', ', $columns);
}
protected function buildFrom($tables)
{
if (is_string($tables) && strpos($tables, '(') !== false) {
return $tables;
}
if (!is_array($tables)) {
$tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY);
}
foreach ($tables as $i => $table) {
if (strpos($table, '(') === false) {
if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) { // with alias
$tables[$i] = $this->_connection->quoteTableName($matches[1]) . ' ' . $this->_connection->quoteTableName($matches[2]);
}
else {
$tables[$i] = $this->_connection->quoteTableName($table);
}
}
}
return implode(', ', $tables);
}
$this->buildJoin($query->join),
$this->buildWhere($query->where),
$this->buildGroupBy($query->groupBy),
$this->buildHaving($query->having),
$this->buildOrderBy($query->orderBy),
$this->buildLimit($query->offset, $query->limit),
if (isset($query['union']))
$sql .= "\nUNION (\n" . (is_array($query['union']) ? implode("\n) UNION (\n", $query['union']) : $query['union']) . ')';
return $sql;
}
/**
* Sets the WHERE part of the query.
*
* The method requires a $conditions parameter, and optionally a $params parameter
* specifying the values to be bound to the query.
*
* The $conditions parameter should be either a string (e.g. 'id=1') or an array.
* If the latter, it must be of the format <code>array(operator, operand1, operand2, ...)</code>,
* where the operator can be one of the followings, and the possible operands depend on the corresponding
* operator:
* <ul>
* <li><code>and</code>: the operands should be concatenated together using AND. For example,
* array('and', 'id=1', 'id=2') will generate 'id=1 AND id=2'. If an operand is an array,
* it will be converted into a string using the same rules described here. For example,
* array('and', 'type=1', array('or', 'id=1', 'id=2')) will generate 'type=1 AND (id=1 OR id=2)'.
* The method will NOT do any quoting or escaping.</li>
* <li><code>or</code>: similar as the <code>and</code> operator except that the operands are concatenated using OR.</li>
* <li><code>in</code>: operand 1 should be a column or DB expression, and operand 2 be an array representing
* the range of the values that the column or DB expression should be in. For example,
* array('in', 'id', array(1,2,3)) will generate 'id IN (1,2,3)'.
* The method will properly quote the column name and escape values in the range.</li>
* <li><code>not in</code>: similar as the <code>in</code> operator except that IN is replaced with NOT IN in the generated condition.</li>
* <li><code>like</code>: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
* the values that the column or DB expression should be like.
* For example, array('like', 'name', '%tester%') will generate "name LIKE '%tester%'".
* When the value range is given as an array, multiple LIKE predicates will be generated and concatenated using AND.
* For example, array('like', 'name', array('%test%', '%sample%')) will generate
* "name LIKE '%test%' AND name LIKE '%sample%'".
* The method will properly quote the column name and escape values in the range.</li>
* <li><code>not like</code>: similar as the <code>like</code> operator except that LIKE is replaced with NOT LIKE in the generated condition.</li>
* <li><code>or like</code>: similar as the <code>like</code> operator except that OR is used to concatenated the LIKE predicates.</li>
* <li><code>or not like</code>: similar as the <code>not like</code> operator except that OR is used to concatenated the NOT LIKE predicates.</li>
* </ul>
* @param mixed $conditions the conditions that should be put in the WHERE part.
* @param array $params the parameters (name=>value) to be bound to the query
* @return Command the command object itself
* @since 1.1.6
*/
public function where($conditions, $params = array())
{
$this->_query['where'] = $this->processConditions($conditions);
foreach ($params as $name => $value)
$this->params[$name] = $value;
return $this;
}
/**
* Appends an INNER JOIN part to the query.
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @param mixed $conditions the join condition that should appear in the ON part.
* Please refer to {@link where} on how to specify conditions.
* @param array $params the parameters (name=>value) to be bound to the query
* @return Command the command object itself
* @since 1.1.6
*/
public function join($table, $conditions, $params = array())
{
return $this->joinInternal('join', $table, $conditions, $params);
}
/**
* Sets the GROUP BY part of the query.
* @param mixed $columns the columns to be grouped by.
* Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return Command the command object itself
* @since 1.1.6
*/
public function group($columns)
{
if (is_string($columns) && strpos($columns, '(') !== false)
$this->_query['group'] = $columns;
else
{
if (!is_array($columns))
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
foreach ($columns as $i => $column)
{
if (is_object($column))
$columns[$i] = (string)$column;
elseif (strpos($column, '(') === false)
$columns[$i] = $this->_connection->quoteColumnName($column);
}
$this->_query['group'] = implode(', ', $columns);
}
return $this;
}
/**
* Sets the HAVING part of the query.
* @param mixed $conditions the conditions to be put after HAVING.
* Please refer to {@link where} on how to specify conditions.
* @param array $params the parameters (name=>value) to be bound to the query
* @return Command the command object itself
* @since 1.1.6
*/
public function having($conditions, $params = array())
{
$this->_query['having'] = $this->processConditions($conditions);
foreach ($params as $name => $value)
$this->params[$name] = $value;
return $this;
}
/**
* Sets the ORDER BY part of the query.
* @param mixed $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 Command the command object itself
* @since 1.1.6
*/
public function order($columns)
{
if (is_string($columns) && strpos($columns, '(') !== false)
$this->_query['order'] = $columns;
else
{
if (!is_array($columns))
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
foreach ($columns as $i => $column)
{
if (is_object($column))
$columns[$i] = (string)$column;
elseif (strpos($column, '(') === false)
{
if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches))
$columns[$i] = $this->_connection->quoteColumnName($matches[1]) . ' ' . strtoupper($matches[2]);
else
$columns[$i] = $this->_connection->quoteColumnName($column);
}
}
$this->_query['order'] = implode(', ', $columns);
}
return $this;
}
/**
* Sets the LIMIT part of the query.
* @param integer $limit the limit
* @param integer $offset the offset
* @return Command the command object itself
* @since 1.1.6
*/
public function limit($limit, $offset = null)
{
$this->_query['limit'] = (int)$limit;
if ($offset !== null)
$this->offset($offset);
return $this;
}
/**
* Appends a SQL statement using UNION operator.
* @param string $sql the SQL statement to be appended using UNION
* @return Command the command object itself
* @since 1.1.6
*/
public function union($sql)
{
if (isset($this->_query['union']) && is_string($this->_query['union']))
$this->_query['union'] = array($this->_query['union']);
$this->_query['union'][] = $sql;
return $this;
}
/**
* Generates the condition string that will be put in the WHERE part
* @param mixed $conditions the conditions that will be put in the WHERE part.
* @return string the condition string to put in the WHERE part
*/
private function buildConditions($conditions)
{
if (!is_array($conditions))
return $conditions;
elseif ($conditions === array())
return '';
$n = count($conditions);
$operator = strtoupper($conditions[0]);
if ($operator === 'OR' || $operator === 'AND')
{
$parts = array();
for ($i = 1;$i < $n;++$i)
{
$condition = $this->processConditions($conditions[$i]);
if ($condition !== '')
$parts[] = '(' . $condition . ')';
}
return $parts === array() ? '' : implode(' ' . $operator . ' ', $parts);
}
if (!isset($conditions[1], $conditions[2]))
return '';
$column = $conditions[1];
if (strpos($column, '(') === false)
$column = $this->_connection->quoteColumnName($column);
$values = $conditions[2];
if (!is_array($values))
$values = array($values);
if ($operator === 'IN' || $operator === 'NOT IN')
{
if ($values === array())
return $operator === 'IN' ? '0=1' : '';
foreach ($values as $i => $value)
{
if (is_string($value))
$values[$i] = $this->_connection->quoteValue($value);
else
$values[$i] = (string)$value;
}
return $column . ' ' . $operator . ' (' . implode(', ', $values) . ')';
}
if ($operator === 'LIKE' || $operator === 'NOT LIKE' || $operator === 'OR LIKE' || $operator === 'OR NOT LIKE')
{
if ($values === array())
return $operator === 'LIKE' || $operator === 'OR LIKE' ? '0=1' : '';
if ($operator === 'LIKE' || $operator === 'NOT LIKE')
$andor = ' AND ';
else
{
$andor = ' OR ';
$operator = $operator === 'OR LIKE' ? 'LIKE' : 'NOT LIKE';
}
$expressions = array();
foreach ($values as $value)
$expressions[] = $column . ' ' . $operator . ' ' . $this->_connection->quoteValue($value);
return implode($andor, $expressions);
}
throw new CDbException(Yii::t('yii', 'Unknown operator "{operator}".', array('{operator}' => $operator)));
}
/**
* Appends an JOIN part to the query.
* @param string $type the join type ('join', 'left join', 'right join', 'cross join', 'natural join')
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @param mixed $conditions the join condition that should appear in the ON part.
* Please refer to {@link where} on how to specify conditions.
* @param array $params the parameters (name=>value) to be bound to the query
* @return Command the command object itself
* @since 1.1.6
*/
private function joinInternal($type, $table, $conditions = '', $params = array())
{
if (strpos($table, '(') === false)
{
if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) // with alias
$table = $this->_connection->quoteTableName($matches[1]) . ' ' . $this->_connection->quoteTableName($matches[2]);
else
$table = $this->_connection->quoteTableName($table);
}
$conditions = $this->processConditions($conditions);
if ($conditions != '')
$conditions = ' ON ' . $conditions;
if (isset($this->_query['join']) && is_string($this->_query['join']))
$this->_query['join'] = array($this->_query['join']);
$this->_query['join'][] = strtoupper($type) . ' ' . $table . $conditions;
foreach ($params as $name => $value)
$this->params[$name] = $value;
return $this;
}
}

556
framework/db/dao/Schema.php

@ -0,0 +1,556 @@
<?php
/**
* CDbSchema class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CDbSchema is the base class for retrieving metadata information.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CDbSchema.php 3359 2011-07-18 11:25:17Z qiang.xue $
* @package system.db.schema
* @since 1.0
*/
abstract class CDbSchema extends CComponent
{
/**
* @var array the abstract column types mapped to physical column types.
* @since 1.1.6
*/
public $columnTypes = array();
private $_tableNames = array();
private $_tables = array();
private $_connection;
private $_builder;
private $_cacheExclude = array();
/**
* Loads the metadata for the specified table.
* @param string $name table name
* @return CDbTableSchema driver dependent table metadata, null if the table does not exist.
*/
abstract protected function loadTable($name);
/**
* Constructor.
* @param CDbConnection $conn database connection.
*/
public function __construct($conn)
{
$this->_connection = $conn;
foreach ($conn->schemaCachingExclude as $name)
$this->_cacheExclude[$name] = true;
}
/**
* @return CDbConnection database connection. The connection is active.
*/
public function getDbConnection()
{
return $this->_connection;
}
/**
* Obtains the metadata for the named table.
* @param string $name table name
* @return CDbTableSchema table metadata. Null if the named table does not exist.
*/
public function getTable($name)
{
if (isset($this->_tables[$name]))
return $this->_tables[$name];
else
{
if ($this->_connection->tablePrefix !== null && strpos($name, '{{') !== false)
$realName = preg_replace('/\{\{(.*?)\}\}/', $this->_connection->tablePrefix . '$1', $name);
else
$realName = $name;
// temporarily disable query caching
if ($this->_connection->queryCachingDuration > 0)
{
$qcDuration = $this->_connection->queryCachingDuration;
$this->_connection->queryCachingDuration = 0;
}
if (!isset($this->_cacheExclude[$name]) && ($duration = $this->_connection->schemaCachingDuration) > 0 && $this->_connection->schemaCacheID !== false && ($cache = Yii::app()->getComponent($this->_connection->schemaCacheID)) !== null)
{
$key = 'yii:dbschema' . $this->_connection->connectionString . ':' . $this->_connection->username . ':' . $name;
if (($table = $cache->get($key)) === false)
{
$table = $this->loadTable($realName);
if ($table !== null)
$cache->set($key, $table, $duration);
}
$this->_tables[$name] = $table;
}
else
$this->_tables[$name] = $table = $this->loadTable($realName);
if (isset($qcDuration)) // re-enable query caching
$this->_connection->queryCachingDuration = $qcDuration;
return $table;
}
}
/**
* Returns the metadata for all tables in the database.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
* @return array the metadata for all tables in the database.
* Each array element is an instance of {@link CDbTableSchema} (or its child class).
* The array keys are table names.
* @since 1.0.2
*/
public function getTables($schema = '')
{
$tables = array();
foreach ($this->getTableNames($schema) as $name)
{
if (($table = $this->getTable($name)) !== null)
$tables[$name] = $table;
}
return $tables;
}
/**
* Returns all table names in the database.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
* If not empty, the returned table names will be prefixed with the schema name.
* @return array all table names in the database.
* @since 1.0.2
*/
public function getTableNames($schema = '')
{
if (!isset($this->_tableNames[$schema]))
$this->_tableNames[$schema] = $this->findTableNames($schema);
return $this->_tableNames[$schema];
}
/**
* @return CDbCommandBuilder the SQL command builder for this connection.
*/
public function getCommandBuilder()
{
if ($this->_builder !== null)
return $this->_builder;
else
return $this->_builder = $this->createCommandBuilder();
}
/**
* Refreshes the schema.
* This method resets the loaded table metadata and command builder
* so that they can be recreated to reflect the change of schema.
*/
public function refresh()
{
if (($duration = $this->_connection->schemaCachingDuration) > 0 && $this->_connection->schemaCacheID !== false && ($cache = Yii::app()->getComponent($this->_connection->schemaCacheID)) !== null)
{
foreach (array_keys($this->_tables) as $name)
{
if (!isset($this->_cacheExclude[$name]))
{
$key = 'yii:dbschema' . $this->_connection->connectionString . ':' . $this->_connection->username . ':' . $name;
$cache->delete($key);
}
}
}
$this->_tables = array();
$this->_tableNames = array();
$this->_builder = null;
}
/**
* Quotes a table name for use in a query.
* If the table name contains schema prefix, the prefix will also be properly quoted.
* @param string $name table name
* @return string the properly quoted table name
* @see quoteSimpleTableName
*/
public function quoteTableName($name)
{
if (strpos($name, '.') === false)
return $this->quoteSimpleTableName($name);
$parts = explode('.', $name);
foreach ($parts as $i => $part)
$parts[$i] = $this->quoteSimpleTableName($part);
return implode('.', $parts);
}
/**
* Quotes a simple table name for use in a query.
* A simple table name does not schema prefix.
* @param string $name table name
* @return string the properly quoted table name
* @since 1.1.6
*/
public function quoteSimpleTableName($name)
{
return "'" . $name . "'";
}
/**
* Quotes a column name for use in a query.
* If the column name contains prefix, the prefix will also be properly quoted.
* @param string $name column name
* @return string the properly quoted column name
* @see quoteSimpleColumnName
*/
public function quoteColumnName($name)
{
if (($pos = strrpos($name, '.')) !== false)
{
$prefix = $this->quoteTableName(substr($name, 0, $pos)) . '.';
$name = substr($name, $pos + 1);
}
else
$prefix = '';
return $prefix . ($name === '*' ? $name : $this->quoteSimpleColumnName($name));
}
/**
* Quotes a simple column name for use in a query.
* A simple column name does not contain prefix.
* @param string $name column name
* @return string the properly quoted column name
* @since 1.1.6
*/
public function quoteSimpleColumnName($name)
{
return '"' . $name . '"';
}
/**
* Compares two table names.
* The table names can be either quoted or unquoted. This method
* will consider both cases.
* @param string $name1 table name 1
* @param string $name2 table name 2
* @return boolean whether the two table names refer to the same table.
*/
public function compareTableNames($name1, $name2)
{
$name1 = str_replace(array('"', '`', "'"), '', $name1);
$name2 = str_replace(array('"', '`', "'"), '', $name2);
if (($pos = strrpos($name1, '.')) !== false)
$name1 = substr($name1, $pos + 1);
if (($pos = strrpos($name2, '.')) !== false)
$name2 = substr($name2, $pos + 1);
if ($this->_connection->tablePrefix !== null)
{
if (strpos($name1, '{') !== false)
$name1 = $this->_connection->tablePrefix . str_replace(array('{', '}'), '', $name1);
if (strpos($name2, '{') !== false)
$name2 = $this->_connection->tablePrefix . str_replace(array('{', '}'), '', $name2);
}
return $name1 === $name2;
}
/**
* Resets 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 CDbTableSchema $table the table schema 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.
* @since 1.1
*/
public function resetSequence($table, $value = null)
{
}
/**
* Enables or disables integrity check.
* @param boolean $check whether to turn on or off the integrity check.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
* @since 1.1
*/
public function checkIntegrity($check = true, $schema = '')
{
}
/**
* Creates a command builder for the database.
* This method may be overridden by child classes to create a DBMS-specific command builder.
* @return CDbCommandBuilder command builder instance
*/
protected function createCommandBuilder()
{
return new CDbCommandBuilder($this);
}
/**
* Returns all table names in the database.
* This method should be overridden by child classes in order to support this feature
* because the default implementation simply throws an exception.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
* If not empty, the returned table names will be prefixed with the schema name.
* @return array all table names in the database.
* @since 1.0.2
*/
protected function findTableNames($schema = '')
{
throw new CDbException(Yii::t('yii', '{class} does not support fetching all table names.',
array('{class}' => get_class($this))));
}
/**
* Converts an abstract column type into a physical column type.
* The conversion is done using the type map specified in {@link columnTypes}.
* These abstract column types are supported (using MySQL as example to explain the corresponding
* physical types):
* <ul>
* <li>pk: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY"</li>
* <li>string: string type, will be converted into "varchar(255)"</li>
* <li>text: a long string type, will be converted into "text"</li>
* <li>integer: integer type, will be converted into "int(11)"</li>
* <li>boolean: boolean type, will be converted into "tinyint(1)"</li>
* <li>float: float number type, will be converted into "float"</li>
* <li>decimal: decimal number type, will be converted into "decimal"</li>
* <li>datetime: datetime type, will be converted into "datetime"</li>
* <li>timestamp: timestamp type, will be converted into "timestamp"</li>
* <li>time: time type, will be converted into "time"</li>
* <li>date: date type, will be converted into "date"</li>
* <li>binary: binary data type, will be converted into "blob"</li>
* </ul>
*
* If the abstract type contains two or more parts separated by spaces (e.g. "string NOT NULL"), then only
* the first part will be converted, and the rest of the parts will be appended to the conversion result.
* For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'.
* @param string $type abstract column type
* @return string physical column type.
* @since 1.1.6
*/
public function getColumnType($type)
{
if (isset($this->columnTypes[$type]))
return $this->columnTypes[$type];
elseif (($pos = strpos($type, ' ')) !== false)
{
$t = substr($type, 0, $pos);
return (isset($this->columnTypes[$t]) ? $this->columnTypes[$t] : $t) . substr($type, $pos);
}
else
return $type;
}
/**
* Builds a SQL statement 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 {@link getColumnType} method will be invoked to convert any abstract type into a physical one.
*
* 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 string the SQL statement for creating a new DB table.
* @since 1.1.6
*/
public function createTable($table, $columns, $options = null)
{
$cols = array();
foreach ($columns as $name => $type)
{
if (is_string($name))
$cols[] = "\t" . $this->quoteColumnName($name) . ' ' . $this->getColumnType($type);
else
$cols[] = "\t" . $type;
}
$sql = "CREATE TABLE " . $this->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)";
return $options === null ? $sql : $sql . ' ' . $options;
}
/**
* Builds a SQL statement 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 string the SQL statement for renaming a DB table.
* @since 1.1.6
*/
public function renameTable($table, $newName)
{
return 'RENAME TABLE ' . $this->quoteTableName($table) . ' TO ' . $this->quoteTableName($newName);
}
/**
* Builds a SQL statement for dropping a DB table.
* @param string $table the table to be dropped. The name will be properly quoted by the method.
* @return string the SQL statement for dropping a DB table.
* @since 1.1.6
*/
public function dropTable($table)
{
return "DROP TABLE " . $this->quoteTableName($table);
}
/**
* Builds a SQL statement for truncating a DB table.
* @param string $table the table to be truncated. The name will be properly quoted by the method.
* @return string the SQL statement for truncating a DB table.
* @since 1.1.6
*/
public function truncateTable($table)
{
return "TRUNCATE TABLE " . $this->quoteTableName($table);
}
/**
* Builds a SQL statement 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. The {@link getColumnType} method will be invoked to convert abstract column type (if any)
* into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
* For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
* @return string the SQL statement for adding a new column.
* @since 1.1.6
*/
public function addColumn($table, $column, $type)
{
return 'ALTER TABLE ' . $this->quoteTableName($table)
. ' ADD ' . $this->quoteColumnName($column) . ' '
. $this->getColumnType($type);
}
/**
* Builds a SQL statement 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 string the SQL statement for dropping a DB column.
* @since 1.1.6
*/
public function dropColumn($table, $column)
{
return "ALTER TABLE " . $this->quoteTableName($table)
. " DROP COLUMN " . $this->quoteColumnName($column);
}
/**
* Builds a SQL statement 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 $name 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 string the SQL statement for renaming a DB column.
* @since 1.1.6
*/
public function renameColumn($table, $name, $newName)
{
return "ALTER TABLE " . $this->quoteTableName($table)
. " RENAME COLUMN " . $this->quoteColumnName($name)
. " TO " . $this->quoteColumnName($newName);
}
/**
* Builds a SQL statement 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 new column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any)
* into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
* For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
* @return string the SQL statement for changing the definition of a column.
* @since 1.1.6
*/
public function alterColumn($table, $column, $type)
{
return 'ALTER TABLE ' . $this->quoteTableName($table) . ' CHANGE '
. $this->quoteColumnName($column) . ' '
. $this->quoteColumnName($column) . ' '
. $this->getColumnType($type);
}
/**
* Builds a SQL statement 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 string the SQL statement for adding a foreign key constraint to an existing table.
* @since 1.1.6
*/
public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
{
$columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
foreach ($columns as $i => $col)
$columns[$i] = $this->quoteColumnName($col);
$refColumns = preg_split('/\s*,\s*/', $refColumns, -1, PREG_SPLIT_NO_EMPTY);
foreach ($refColumns as $i => $col)
$refColumns[$i] = $this->quoteColumnName($col);
$sql = 'ALTER TABLE ' . $this->quoteTableName($table)
. ' ADD CONSTRAINT ' . $this->quoteColumnName($name)
. ' FOREIGN KEY (' . implode(', ', $columns) . ')'
. ' REFERENCES ' . $this->quoteTableName($refTable)
. ' (' . implode(', ', $refColumns) . ')';
if ($delete !== null)
$sql .= ' ON DELETE ' . $delete;
if ($update !== null)
$sql .= ' ON UPDATE ' . $update;
return $sql;
}
/**
* Builds a SQL statement 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 string the SQL statement for dropping a foreign key constraint.
* @since 1.1.6
*/
public function dropForeignKey($name, $table)
{
return 'ALTER TABLE ' . $this->quoteTableName($table)
. ' DROP CONSTRAINT ' . $this->quoteColumnName($name);
}
/**
* Builds a SQL statement 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 $column the column(s) that should be included in the index. If there are multiple columns, please separate them
* by commas. Each column name will be properly quoted by the method, unless a parenthesis is found in the name.
* @param boolean $unique whether to add UNIQUE constraint on the created index.
* @return string the SQL statement for creating a new index.
* @since 1.1.6
*/
public function createIndex($name, $table, $column, $unique = false)
{
$cols = array();
$columns = preg_split('/\s*,\s*/', $column, -1, PREG_SPLIT_NO_EMPTY);
foreach ($columns as $col)
{
if (strpos($col, '(') !== false)
$cols[] = $col;
else
$cols[] = $this->quoteColumnName($col);
}
return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ')
. $this->quoteTableName($name) . ' ON '
. $this->quoteTableName($table) . ' (' . implode(', ', $cols) . ')';
}
/**
* Builds a SQL statement 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 string the SQL statement for dropping an index.
* @since 1.1.6
*/
public function dropIndex($name, $table)
{
return 'DROP INDEX ' . $this->quoteTableName($name) . ' ON ' . $this->quoteTableName($table);
}
}

76
framework/db/dao/TableSchema.php

@ -0,0 +1,76 @@
<?php
/**
* CDbTableSchema class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CDbTableSchema is the base class for representing the metadata of a database table.
*
* It may be extended by different DBMS driver to provide DBMS-specific table metadata.
*
* CDbTableSchema provides the following information about a table:
* <ul>
* <li>{@link name}</li>
* <li>{@link rawName}</li>
* <li>{@link columns}</li>
* <li>{@link primaryKey}</li>
* <li>{@link foreignKeys}</li>
* <li>{@link sequenceName}</li>
* </ul>
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CDbTableSchema.php 2799 2011-01-01 19:31:13Z qiang.xue $
* @package system.db.schema
* @since 1.0
*/
class CDbTableSchema extends CComponent
{
/**
* @var string name of this table.
*/
public $name;
/**
* @var string raw name of this table. This is the quoted version of table name with optional schema name. It can be directly used in SQLs.
*/
public $rawName;
/**
* @var string|array primary key name of this table. If composite key, an array of key names is returned.
*/
public $primaryKey;
/**
* @var string sequence name for the primary key. Null if no sequence.
*/
public $sequenceName;
/**
* @var array foreign keys of this table. The array is indexed by column name. Each value is an array of foreign table name and foreign column name.
*/
public $foreignKeys = array();
/**
* @var array column metadata of this table. Each array element is a CDbColumnSchema object, indexed by column names.
*/
public $columns = array();
/**
* Gets the named column metadata.
* This is a convenient method for retrieving a named column even if it does not exist.
* @param string $name column name
* @return CDbColumnSchema metadata of the named column. Null if the named column does not exist.
*/
public function getColumn($name)
{
return isset($this->columns[$name]) ? $this->columns[$name] : null;
}
/**
* @return array list of column names
*/
public function getColumnNames()
{
return array_keys($this->columns);
}
}
Loading…
Cancel
Save