From 05c7915d8a81aaa9928362b81732b998f40a0bcd Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 11 Aug 2011 22:07:29 -0400 Subject: [PATCH] w --- framework/db/dao/ColumnSchema.php | 148 ++++++++++ framework/db/dao/Command.php | 4 +- framework/db/dao/Expression.php | 61 +++++ framework/db/dao/Query.php | 520 +++++++++++++++++++++++++++++++++++ framework/db/dao/QueryBuilder.php | 402 +++++++++++++++++++++++++++ framework/db/dao/Schema.php | 556 ++++++++++++++++++++++++++++++++++++++ framework/db/dao/TableSchema.php | 76 ++++++ 7 files changed, 1766 insertions(+), 1 deletion(-) create mode 100644 framework/db/dao/ColumnSchema.php create mode 100644 framework/db/dao/Expression.php create mode 100644 framework/db/dao/Query.php create mode 100644 framework/db/dao/QueryBuilder.php create mode 100644 framework/db/dao/Schema.php create mode 100644 framework/db/dao/TableSchema.php diff --git a/framework/db/dao/ColumnSchema.php b/framework/db/dao/ColumnSchema.php new file mode 100644 index 0000000..a0e48e3 --- /dev/null +++ b/framework/db/dao/ColumnSchema.php @@ -0,0 +1,148 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 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 + * @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; + } + } +} diff --git a/framework/db/dao/Command.php b/framework/db/dao/Command.php index 535559c..f275677 100644 --- a/framework/db/dao/Command.php +++ b/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 * @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. diff --git a/framework/db/dao/Expression.php b/framework/db/dao/Expression.php new file mode 100644 index 0000000..15ecd1b --- /dev/null +++ b/framework/db/dao/Expression.php @@ -0,0 +1,61 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 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 + * @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; + } +} \ No newline at end of file diff --git a/framework/db/dao/Query.php b/framework/db/dao/Query.php new file mode 100644 index 0000000..6f623c9 --- /dev/null +++ b/framework/db/dao/Query.php @@ -0,0 +1,520 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 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 + * @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, age>31 AND team=1. + */ + 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, 'projectID, teamID'. + */ + public $groupBy; + /** + * @var string how to join with other tables. This refers to the JOIN clause in an SQL statement. + * For example, 'LEFT JOIN users ON users.id=authorID'. + */ + public $join; + /** + * @var string the condition to be applied with GROUP-BY clause. + * For example, 'SUM(revenue)<50000'. + */ + public $having; + /** + * @var array list of query parameter values indexed by parameter placeholders. + * For example, array(':name'=>'Dan', ':age'=>31). + */ + 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: + *
    + *
  • <: the column must be less than the given value.
  • + *
  • >: the column must be greater than the given value.
  • + *
  • <=: the column must be less than or equal to the given value.
  • + *
  • >=: the column must be greater than or equal to the given value.
  • + *
  • <>: 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.
  • + *
  • =: the column must be equal to the given value.
  • + *
  • 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.
  • + *
+ * + * 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; + } +} diff --git a/framework/db/dao/QueryBuilder.php b/framework/db/dao/QueryBuilder.php new file mode 100644 index 0000000..2762841 --- /dev/null +++ b/framework/db/dao/QueryBuilder.php @@ -0,0 +1,402 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 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 + * @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 array(operator, operand1, operand2, ...), + * where the operator can be one of the followings, and the possible operands depend on the corresponding + * operator: + *
    + *
  • and: 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.
  • + *
  • or: similar as the and operator except that the operands are concatenated using OR.
  • + *
  • in: 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.
  • + *
  • not in: similar as the in operator except that IN is replaced with NOT IN in the generated condition.
  • + *
  • like: 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.
  • + *
  • not like: similar as the like operator except that LIKE is replaced with NOT LIKE in the generated condition.
  • + *
  • or like: similar as the like operator except that OR is used to concatenated the LIKE predicates.
  • + *
  • or not like: similar as the not like operator except that OR is used to concatenated the NOT LIKE predicates.
  • + *
+ * @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; + } +} diff --git a/framework/db/dao/Schema.php b/framework/db/dao/Schema.php new file mode 100644 index 0000000..7f21a69 --- /dev/null +++ b/framework/db/dao/Schema.php @@ -0,0 +1,556 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CDbSchema is the base class for retrieving metadata information. + * + * @author Qiang Xue + * @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): + *
    + *
  • pk: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY"
  • + *
  • string: string type, will be converted into "varchar(255)"
  • + *
  • text: a long string type, will be converted into "text"
  • + *
  • integer: integer type, will be converted into "int(11)"
  • + *
  • boolean: boolean type, will be converted into "tinyint(1)"
  • + *
  • float: float number type, will be converted into "float"
  • + *
  • decimal: decimal number type, will be converted into "decimal"
  • + *
  • datetime: datetime type, will be converted into "datetime"
  • + *
  • timestamp: timestamp type, will be converted into "timestamp"
  • + *
  • time: time type, will be converted into "time"
  • + *
  • date: date type, will be converted into "date"
  • + *
  • binary: binary data type, will be converted into "blob"
  • + *
+ * + * 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); + } +} diff --git a/framework/db/dao/TableSchema.php b/framework/db/dao/TableSchema.php new file mode 100644 index 0000000..ece39f5 --- /dev/null +++ b/framework/db/dao/TableSchema.php @@ -0,0 +1,76 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 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: + *
    + *
  • {@link name}
  • + *
  • {@link rawName}
  • + *
  • {@link columns}
  • + *
  • {@link primaryKey}
  • + *
  • {@link foreignKeys}
  • + *
  • {@link sequenceName}
  • + *
+ * + * @author Qiang Xue + * @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); + } +}