diff --git a/framework/base/NotSupportedException.php b/framework/base/NotSupportedException.php new file mode 100644 index 0000000..aa2badb --- /dev/null +++ b/framework/base/NotSupportedException.php @@ -0,0 +1,21 @@ + + * @since 2.0 + */ +class NotSupportedException extends \Exception +{ +} + diff --git a/framework/db/ColumnSchema.php b/framework/db/ColumnSchema.php index 14bef19..44e6cb0 100644 --- a/framework/db/ColumnSchema.php +++ b/framework/db/ColumnSchema.php @@ -22,15 +22,11 @@ class ColumnSchema extends \yii\base\Component */ public $name; /** - * @var string the quoted name of this column. - */ - public $quotedName; - /** * @var boolean whether this column can be null. */ public $allowNull; /** - * @var string logical type of this column. Possible logic types include: + * @var string abstract type of this column. Possible abstract types include: * string, text, boolean, smallint, integer, bigint, float, decimal, datetime, * timestamp, time, date, binary, and money. */ @@ -77,31 +73,11 @@ class ColumnSchema extends \yii\base\Component * when [[type]] is `smallint`, `integer` or `bigint`. */ public $unsigned; - /** - * Extracts the PHP type from DB type. + * @var string comment of this column. Not all DBMS support this. */ - public function resolvePhpType() - { - static $typeMap = array( // logical type => php type - 'smallint' => 'integer', - 'integer' => 'integer', - 'bigint' => 'integer', - 'boolean' => 'boolean', - 'float' => 'double', - ); - if (isset($typeMap[$this->type])) { - if ($this->type === 'bigint') { - $this->phpType = PHP_INT_SIZE == 8 && !$this->unsigned ? 'integer' : 'string'; - } elseif ($this->type === 'integer') { - $this->phpType = PHP_INT_SIZE == 4 && $this->unsigned ? 'string' : 'integer'; - } else { - $this->phpType = $typeMap[$this->type]; - } - } else { - $this->phpType = 'string'; - } - } + public $comment; + /** * Converts the input value according to [[phpType]]. diff --git a/framework/db/Schema.php b/framework/db/Schema.php index c8e32ed..a02ec28 100644 --- a/framework/db/Schema.php +++ b/framework/db/Schema.php @@ -9,7 +9,8 @@ namespace yii\db; -use yii\db\Exception; +use yii\base\NotSupportedException; +use yii\base\BadCallException; /** * Schema is the base class for concrete DBMS-specific schema classes. @@ -113,15 +114,20 @@ abstract class Schema extends \yii\base\Object /** * 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. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name. + * @param boolean $refresh whether to fetch the latest available table schemas. If this is false, + * cached data may be returned if available. * @return array the metadata for all tables in the database. - * Each array element is an instance of [[TableSchema]] (or its child class). + * Each array element is an instance of [[TableSchema]] or its child class. */ - public function getTableSchemas($schema = '') + public function getTableSchemas($schema = '', $refresh = false) { $tables = array(); - foreach ($this->getTableNames($schema) as $name) { - if (($table = $this->getTableSchema($name)) !== null) { + foreach ($this->getTableNames($schema, $refresh) as $name) { + if ($schema !== '') { + $name = $schema . '.' . $name; + } + if (($table = $this->getTableSchema($name, $refresh)) !== null) { $tables[] = $table; } } @@ -130,7 +136,7 @@ abstract class Schema extends \yii\base\Object /** * 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. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name. * If not empty, the returned table names will be prefixed with the schema name. * @param boolean $refresh whether to fetch the latest available table names. If this is false, * table names fetched previously (if available) will be returned. @@ -168,6 +174,7 @@ abstract class Schema extends \yii\base\Object $cache->delete($this->getCacheKey($name)); } } + $this->_tableNames = array(); $this->_tables = array(); } @@ -186,18 +193,19 @@ abstract class Schema extends \yii\base\Object * 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. + * @return array all table names in the database. The names have NO the schema name prefix. + * @throws NotSupportedException if this method is called */ protected function findTableNames($schema = '') { - throw new Exception(get_class($this) . ' does not support fetching all table names.'); + throw new NotSupportedException(get_class($this) . ' does not support fetching all table names.'); } /** * Returns the ID of the last inserted row or sequence value. * @param string $sequenceName name of the sequence object (required by some DBMS) * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object + * @throws BadCallException if the DB connection is not active * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php */ public function getLastInsertID($sequenceName = '') @@ -205,11 +213,10 @@ abstract class Schema extends \yii\base\Object if ($this->connection->isActive) { return $this->connection->pdo->lastInsertId($sequenceName); } else { - throw new Exception('DB Connection is not active.'); + throw new BadCallException('DB Connection is not active.'); } } - /** * Quotes a string value for use in a query. * Note that if the parameter is not a string, it will be returned without change. @@ -319,4 +326,31 @@ abstract class Schema extends \yii\base\Object return $name; } } + + /** + * Extracts the PHP type from abstract DB type. + * @param string $type abstract DB type + * @return string PHP type name + */ + protected function getColumnPhpType($type) + { + static $typeMap = array( // abstract type => php type + 'smallint' => 'integer', + 'integer' => 'integer', + 'bigint' => 'integer', + 'boolean' => 'boolean', + 'float' => 'double', + ); + if (isset($typeMap[$type])) { + if ($type === 'bigint') { + return PHP_INT_SIZE == 8 && !$this->unsigned ? 'integer' : 'string'; + } elseif ($type === 'integer') { + return PHP_INT_SIZE == 4 && $this->unsigned ? 'string' : 'integer'; + } else { + return $typeMap[$this->type]; + } + } else { + return 'string'; + } + } } diff --git a/framework/db/TableSchema.php b/framework/db/TableSchema.php index 537c275..344a68d 100644 --- a/framework/db/TableSchema.php +++ b/framework/db/TableSchema.php @@ -9,7 +9,7 @@ namespace yii\db; -use yii\db\Exception; +use yii\base\BadParamException; /** * TableSchema represents the metadata of a database table. @@ -36,10 +36,6 @@ class TableSchema extends \yii\base\Object */ public $name; /** - * @var string quoted name of this table. This will include [[schemaName]] if it is not empty. - */ - public $quotedName; - /** * @var string[] primary keys of this table. */ public $primaryKey = array(); @@ -63,6 +59,10 @@ class TableSchema extends \yii\base\Object * @var ColumnSchema[] column metadata of this table. Each array element is a [[ColumnSchema]] object, indexed by column names. */ public $columns = array(); + /** + * @var string comment of this table + */ + public $comment; /** * Gets the named column metadata. @@ -87,7 +87,7 @@ class TableSchema extends \yii\base\Object /** * Manually specifies the primary key for this table. * @param string|array $keys the primary key (can be composite) - * @throws \yii\db\Exception if the specified key cannot be found in the table. + * @throws BadParamException if the specified key cannot be found in the table. */ public function fixPrimaryKey($keys) { @@ -102,7 +102,7 @@ class TableSchema extends \yii\base\Object if (isset($this->columns[$key])) { $this->columns[$key]->isPrimaryKey = true; } else { - throw new Exception("Primary key '$key' cannot be found in table '{$this->name}'."); + throw new BadParamException("Primary key '$key' cannot be found in table '{$this->name}'."); } } } diff --git a/framework/db/mysql/Schema.php b/framework/db/mysql/Schema.php index 25c84fa..82e02f0 100644 --- a/framework/db/mysql/Schema.php +++ b/framework/db/mysql/Schema.php @@ -85,7 +85,7 @@ class Schema extends \yii\db\Schema /** * Loads the metadata for the specified table. * @param string $name table name - * @return \yii\db\TableSchema driver dependent table metadata. Null if the table does not exist. + * @return TableSchema driver dependent table metadata. Null if the table does not exist. */ protected function loadTableSchema($name) { @@ -95,6 +95,8 @@ class Schema extends \yii\db\Schema if ($this->findColumns($table)) { $this->findConstraints($table); return $table; + } else { + return null; } } @@ -109,64 +111,34 @@ class Schema extends \yii\db\Schema if (isset($parts[1])) { $table->schemaName = $parts[0]; $table->name = $parts[1]; - $table->quotedName = $this->quoteSimpleTableName($table->schemaName) . '.' . $this->quoteSimpleTableName($table->name); } else { $table->name = $parts[0]; - $table->quotedName = $this->quoteSimpleTableName($table->name); } } /** - * Creates a table column. - * @param array $column column metadata - * @return ColumnSchema normalized column metadata + * Loads the column information into a [[ColumnSchema]] object. + * @param array $info column information + * @return ColumnSchema the column schema object */ - protected function createColumn($column) + protected function loadColumn($info) { - $c = new ColumnSchema; - - $c->name = $column['Field']; - $c->quotedName = $this->quoteSimpleColumnName($c->name); - $c->allowNull = $column['Null'] === 'YES'; - $c->isPrimaryKey = strpos($column['Key'], 'PRI') !== false; - $c->autoIncrement = stripos($column['Extra'], 'auto_increment') !== false; - - $c->dbType = $column['Type']; - $this->resolveColumnType($c); - $c->resolvePhpType(); - - $this->resolveColumnDefault($c, $column['Default']); - - return $c; - } + $column = new ColumnSchema; - /** - * Resolves the default value for the column. - * @param \yii\db\ColumnSchema $column the column metadata object - * @param string $value the default value fetched from database - */ - protected function resolveColumnDefault($column, $value) - { - if ($column->type !== 'timestamp' || $value !== 'CURRENT_TIMESTAMP') { - $column->defaultValue = $column->typecast($value); - } - } + $column->name = $info['Field']; + $column->allowNull = $info['Null'] === 'YES'; + $column->isPrimaryKey = strpos($info['Key'], 'PRI') !== false; + $column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false; - /** - * Resolves the abstract data type for the column. - * @param \yii\db\ColumnSchema $column the column metadata object - */ - public function resolveColumnType($column) - { - $column->type = self::TYPE_STRING; + $column->dbType = $info['Type']; $column->unsigned = strpos($column->dbType, 'unsigned') !== false; + $column->type = self::TYPE_STRING; if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) { $type = $matches[1]; if (isset($this->typeMap[$type])) { $column->type = $this->typeMap[$type]; } - if (!empty($matches[2])) { if ($type === 'enum') { $values = explode(',', $matches[2]); @@ -192,23 +164,31 @@ class Schema extends \yii\db\Schema } } } + + $column->phpType = $this->getColumnPhpType($column->type); + + if ($column->type !== 'timestamp' || $info['Default'] !== 'CURRENT_TIMESTAMP') { + $column->defaultValue = $column->typecast($info['Default']); + } + + return $column; } /** * Collects the metadata of table columns. - * @param \yii\db\TableSchema $table the table metadata + * @param TableSchema $table the table metadata * @return boolean whether the table exists in the database */ protected function findColumns($table) { - $sql = 'SHOW COLUMNS FROM ' . $table->quotedName; + $sql = 'SHOW COLUMNS FROM ' . $this->quoteSimpleTableName($table->name); try { $columns = $this->connection->createCommand($sql)->queryAll(); } catch (\Exception $e) { return false; } - foreach ($columns as $column) { - $column = $this->createColumn($column); + foreach ($columns as $info) { + $column = $this->loadColumn($info); $table->columns[$column->name] = $column; if ($column->isPrimaryKey) { $table->primaryKey[] = $column->name; @@ -222,11 +202,11 @@ class Schema extends \yii\db\Schema /** * Collects the foreign key column details for the given table. - * @param \yii\db\TableSchema $table the table metadata + * @param TableSchema $table the table metadata */ protected function findConstraints($table) { - $row = $this->connection->createCommand('SHOW CREATE TABLE ' . $table->quotedName)->queryRow(); + $row = $this->connection->createCommand('SHOW CREATE TABLE ' . $this->quoteSimpleTableName($table->name))->queryRow(); if (isset($row['Create Table'])) { $sql = $row['Create Table']; } else { @@ -250,20 +230,17 @@ class Schema extends \yii\db\Schema /** * 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. + * @return array all table names in the database. The names have NO the schema name prefix. */ protected function findTableNames($schema = '') { - if ($schema === '') { - return $this->connection->createCommand('SHOW TABLES')->queryColumn(); - } - $sql = 'SHOW TABLES FROM ' . $this->quoteSimpleTableName($schema); - $names = $this->connection->createCommand($sql)->queryColumn(); - foreach ($names as $i => $name) { - $names[$i] = $schema . '.' . $name; + $sql = 'SHOW TABLES'; + if ($schema !== '') { + $sql .= ' FROM ' . $this->quoteSimpleTableName($schema); } - return $names; + return $this->connection->createCommand($sql)->queryColumn(); } } diff --git a/framework/db/sqlite/Schema.php b/framework/db/sqlite/Schema.php index adfabdc..903a8b2 100644 --- a/framework/db/sqlite/Schema.php +++ b/framework/db/sqlite/Schema.php @@ -82,7 +82,6 @@ class Schema extends \yii\db\Schema { $table = new TableSchema; $table->name = $name; - $table->quotedName = $this->quoteTableName($name); if ($this->findColumns($table)) { $this->findConstraints($table); @@ -97,7 +96,7 @@ class Schema extends \yii\db\Schema */ protected function findColumns($table) { - $sql = "PRAGMA table_info({$table->quotedName})"; + $sql = "PRAGMA table_info(" . $this->quoteSimpleTableName($table->name) . ')'; $columns = $this->connection->createCommand($sql)->queryAll(); if (empty($columns)) { return false; @@ -124,7 +123,7 @@ class Schema extends \yii\db\Schema */ protected function findConstraints($table) { - $sql = "PRAGMA foreign_key_list({$table->quotedName})"; + $sql = "PRAGMA foreign_key_list(" . $this->quoteSimpleTableName($table->name) . ')'; $keys = $this->connection->createCommand($sql)->queryAll(); foreach ($keys as $key) { $table->foreignKeys[] = array($key['table'], $key['from'] => $key['to']); @@ -140,13 +139,12 @@ class Schema extends \yii\db\Schema { $c = new ColumnSchema; $c->name = $column['name']; - $c->quotedName = $this->quoteSimpleColumnName($c->name); $c->allowNull = !$column['notnull']; $c->isPrimaryKey = $column['pk'] != 0; $c->dbType = $column['type']; $this->resolveColumnType($c); - $c->resolvePhpType(); + $c->phpType = $this->getColumnPhpType($this->type); $this->resolveColumnDefault($c, $column['dflt_value']); diff --git a/framework/validators/UniqueValidator.php b/framework/validators/UniqueValidator.php index 9dd7e84..f8c49b2 100644 --- a/framework/validators/UniqueValidator.php +++ b/framework/validators/UniqueValidator.php @@ -18,11 +18,6 @@ namespace yii\validators; class UniqueValidator extends Validator { /** - * @var boolean whether the comparison is case sensitive. Defaults to true. - * Note, by setting it to false, you are assuming the attribute type is string. - */ - public $caseSensitive = true; - /** * @var boolean whether the attribute value can be null or empty. Defaults to true, * meaning that if the attribute is empty, it is considered valid. */ @@ -76,8 +71,7 @@ class UniqueValidator extends Validator } $query = $className::find(); - $query->where($this->caseSensitive ? "{$column->quotedName}=:value" : "LOWER({$column->quotedName})=LOWER(:value)"); - $query->params(array(':value' => $value)); + $query->where(array($column->name => $value)); if ($object->getIsNewRecord()) { // if current $object isn't in the database yet then it's OK just