Browse Source

DB refactoring.

tags/2.0.0-beta
Qiang Xue 12 years ago
parent
commit
ebecc09839
  1. 21
      framework/base/NotSupportedException.php
  2. 32
      framework/db/ColumnSchema.php
  3. 58
      framework/db/Schema.php
  4. 14
      framework/db/TableSchema.php
  5. 93
      framework/db/mysql/Schema.php
  6. 8
      framework/db/sqlite/Schema.php
  7. 8
      framework/validators/UniqueValidator.php

21
framework/base/NotSupportedException.php

@ -0,0 +1,21 @@
<?php
/**
* NotSupportedException class file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
/**
* NotSupportedException represents an exception caused by accessing features that are not supported.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class NotSupportedException extends \Exception
{
}

32
framework/db/ColumnSchema.php

@ -22,15 +22,11 @@ class ColumnSchema extends \yii\base\Component
*/ */
public $name; public $name;
/** /**
* @var string the quoted name of this column.
*/
public $quotedName;
/**
* @var boolean whether this column can be null. * @var boolean whether this column can be null.
*/ */
public $allowNull; 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, * string, text, boolean, smallint, integer, bigint, float, decimal, datetime,
* timestamp, time, date, binary, and money. * timestamp, time, date, binary, and money.
*/ */
@ -77,31 +73,11 @@ class ColumnSchema extends \yii\base\Component
* when [[type]] is `smallint`, `integer` or `bigint`. * when [[type]] is `smallint`, `integer` or `bigint`.
*/ */
public $unsigned; public $unsigned;
/** /**
* Extracts the PHP type from DB type. * @var string comment of this column. Not all DBMS support this.
*/ */
public function resolvePhpType() public $comment;
{
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';
}
}
/** /**
* Converts the input value according to [[phpType]]. * Converts the input value according to [[phpType]].

58
framework/db/Schema.php

@ -9,7 +9,8 @@
namespace yii\db; 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. * 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. * 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. * @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(); $tables = array();
foreach ($this->getTableNames($schema) as $name) { foreach ($this->getTableNames($schema, $refresh) as $name) {
if (($table = $this->getTableSchema($name)) !== null) { if ($schema !== '') {
$name = $schema . '.' . $name;
}
if (($table = $this->getTableSchema($name, $refresh)) !== null) {
$tables[] = $table; $tables[] = $table;
} }
} }
@ -130,7 +136,7 @@ abstract class Schema extends \yii\base\Object
/** /**
* Returns all table names in the database. * 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. * 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, * @param boolean $refresh whether to fetch the latest available table names. If this is false,
* table names fetched previously (if available) will be returned. * 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)); $cache->delete($this->getCacheKey($name));
} }
} }
$this->_tableNames = array();
$this->_tables = 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 * This method should be overridden by child classes in order to support this feature
* because the default implementation simply throws an exception. * 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. * @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. The names have NO the schema name prefix.
* @return array all table names in the database. * @throws NotSupportedException if this method is called
*/ */
protected function findTableNames($schema = '') 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. * Returns the ID of the last inserted row or sequence value.
* @param string $sequenceName name of the sequence object (required by some DBMS) * @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 * @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 * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php
*/ */
public function getLastInsertID($sequenceName = '') public function getLastInsertID($sequenceName = '')
@ -205,11 +213,10 @@ abstract class Schema extends \yii\base\Object
if ($this->connection->isActive) { if ($this->connection->isActive) {
return $this->connection->pdo->lastInsertId($sequenceName); return $this->connection->pdo->lastInsertId($sequenceName);
} else { } 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. * Quotes a string value for use in a query.
* Note that if the parameter is not a string, it will be returned without change. * 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; 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';
}
}
} }

14
framework/db/TableSchema.php

@ -9,7 +9,7 @@
namespace yii\db; namespace yii\db;
use yii\db\Exception; use yii\base\BadParamException;
/** /**
* TableSchema represents the metadata of a database table. * TableSchema represents the metadata of a database table.
@ -36,10 +36,6 @@ class TableSchema extends \yii\base\Object
*/ */
public $name; 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. * @var string[] primary keys of this table.
*/ */
public $primaryKey = array(); 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. * @var ColumnSchema[] column metadata of this table. Each array element is a [[ColumnSchema]] object, indexed by column names.
*/ */
public $columns = array(); public $columns = array();
/**
* @var string comment of this table
*/
public $comment;
/** /**
* Gets the named column metadata. * Gets the named column metadata.
@ -87,7 +87,7 @@ class TableSchema extends \yii\base\Object
/** /**
* Manually specifies the primary key for this table. * Manually specifies the primary key for this table.
* @param string|array $keys the primary key (can be composite) * @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) public function fixPrimaryKey($keys)
{ {
@ -102,7 +102,7 @@ class TableSchema extends \yii\base\Object
if (isset($this->columns[$key])) { if (isset($this->columns[$key])) {
$this->columns[$key]->isPrimaryKey = true; $this->columns[$key]->isPrimaryKey = true;
} else { } 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}'.");
} }
} }
} }

93
framework/db/mysql/Schema.php

@ -85,7 +85,7 @@ class Schema extends \yii\db\Schema
/** /**
* Loads the metadata for the specified table. * Loads the metadata for the specified table.
* @param string $name table name * @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) protected function loadTableSchema($name)
{ {
@ -95,6 +95,8 @@ class Schema extends \yii\db\Schema
if ($this->findColumns($table)) { if ($this->findColumns($table)) {
$this->findConstraints($table); $this->findConstraints($table);
return $table; return $table;
} else {
return null;
} }
} }
@ -109,64 +111,34 @@ class Schema extends \yii\db\Schema
if (isset($parts[1])) { if (isset($parts[1])) {
$table->schemaName = $parts[0]; $table->schemaName = $parts[0];
$table->name = $parts[1]; $table->name = $parts[1];
$table->quotedName = $this->quoteSimpleTableName($table->schemaName) . '.' . $this->quoteSimpleTableName($table->name);
} else { } else {
$table->name = $parts[0]; $table->name = $parts[0];
$table->quotedName = $this->quoteSimpleTableName($table->name);
} }
} }
/** /**
* Creates a table column. * Loads the column information into a [[ColumnSchema]] object.
* @param array $column column metadata * @param array $info column information
* @return ColumnSchema normalized column metadata * @return ColumnSchema the column schema object
*/ */
protected function createColumn($column) protected function loadColumn($info)
{ {
$c = new ColumnSchema; $column = 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->name = $info['Field'];
* Resolves the default value for the column. $column->allowNull = $info['Null'] === 'YES';
* @param \yii\db\ColumnSchema $column the column metadata object $column->isPrimaryKey = strpos($info['Key'], 'PRI') !== false;
* @param string $value the default value fetched from database $column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false;
*/
protected function resolveColumnDefault($column, $value)
{
if ($column->type !== 'timestamp' || $value !== 'CURRENT_TIMESTAMP') {
$column->defaultValue = $column->typecast($value);
}
}
/** $column->dbType = $info['Type'];
* 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->unsigned = strpos($column->dbType, 'unsigned') !== false; $column->unsigned = strpos($column->dbType, 'unsigned') !== false;
$column->type = self::TYPE_STRING;
if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) { if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
$type = $matches[1]; $type = $matches[1];
if (isset($this->typeMap[$type])) { if (isset($this->typeMap[$type])) {
$column->type = $this->typeMap[$type]; $column->type = $this->typeMap[$type];
} }
if (!empty($matches[2])) { if (!empty($matches[2])) {
if ($type === 'enum') { if ($type === 'enum') {
$values = explode(',', $matches[2]); $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. * 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 * @return boolean whether the table exists in the database
*/ */
protected function findColumns($table) protected function findColumns($table)
{ {
$sql = 'SHOW COLUMNS FROM ' . $table->quotedName; $sql = 'SHOW COLUMNS FROM ' . $this->quoteSimpleTableName($table->name);
try { try {
$columns = $this->connection->createCommand($sql)->queryAll(); $columns = $this->connection->createCommand($sql)->queryAll();
} catch (\Exception $e) { } catch (\Exception $e) {
return false; return false;
} }
foreach ($columns as $column) { foreach ($columns as $info) {
$column = $this->createColumn($column); $column = $this->loadColumn($info);
$table->columns[$column->name] = $column; $table->columns[$column->name] = $column;
if ($column->isPrimaryKey) { if ($column->isPrimaryKey) {
$table->primaryKey[] = $column->name; $table->primaryKey[] = $column->name;
@ -222,11 +202,11 @@ class Schema extends \yii\db\Schema
/** /**
* Collects the foreign key column details for the given table. * 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) 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'])) { if (isset($row['Create Table'])) {
$sql = $row['Create Table']; $sql = $row['Create Table'];
} else { } else {
@ -250,20 +230,17 @@ class Schema extends \yii\db\Schema
/** /**
* Returns all table names in the database. * 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. * @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. The names have NO the schema name prefix.
* @return array all table names in the database.
*/ */
protected function findTableNames($schema = '') protected function findTableNames($schema = '')
{ {
if ($schema === '') { $sql = 'SHOW TABLES';
return $this->connection->createCommand('SHOW TABLES')->queryColumn(); if ($schema !== '') {
} $sql .= ' FROM ' . $this->quoteSimpleTableName($schema);
$sql = 'SHOW TABLES FROM ' . $this->quoteSimpleTableName($schema);
$names = $this->connection->createCommand($sql)->queryColumn();
foreach ($names as $i => $name) {
$names[$i] = $schema . '.' . $name;
} }
return $names; return $this->connection->createCommand($sql)->queryColumn();
} }
} }

8
framework/db/sqlite/Schema.php

@ -82,7 +82,6 @@ class Schema extends \yii\db\Schema
{ {
$table = new TableSchema; $table = new TableSchema;
$table->name = $name; $table->name = $name;
$table->quotedName = $this->quoteTableName($name);
if ($this->findColumns($table)) { if ($this->findColumns($table)) {
$this->findConstraints($table); $this->findConstraints($table);
@ -97,7 +96,7 @@ class Schema extends \yii\db\Schema
*/ */
protected function findColumns($table) protected function findColumns($table)
{ {
$sql = "PRAGMA table_info({$table->quotedName})"; $sql = "PRAGMA table_info(" . $this->quoteSimpleTableName($table->name) . ')';
$columns = $this->connection->createCommand($sql)->queryAll(); $columns = $this->connection->createCommand($sql)->queryAll();
if (empty($columns)) { if (empty($columns)) {
return false; return false;
@ -124,7 +123,7 @@ class Schema extends \yii\db\Schema
*/ */
protected function findConstraints($table) 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(); $keys = $this->connection->createCommand($sql)->queryAll();
foreach ($keys as $key) { foreach ($keys as $key) {
$table->foreignKeys[] = array($key['table'], $key['from'] => $key['to']); $table->foreignKeys[] = array($key['table'], $key['from'] => $key['to']);
@ -140,13 +139,12 @@ class Schema extends \yii\db\Schema
{ {
$c = new ColumnSchema; $c = new ColumnSchema;
$c->name = $column['name']; $c->name = $column['name'];
$c->quotedName = $this->quoteSimpleColumnName($c->name);
$c->allowNull = !$column['notnull']; $c->allowNull = !$column['notnull'];
$c->isPrimaryKey = $column['pk'] != 0; $c->isPrimaryKey = $column['pk'] != 0;
$c->dbType = $column['type']; $c->dbType = $column['type'];
$this->resolveColumnType($c); $this->resolveColumnType($c);
$c->resolvePhpType(); $c->phpType = $this->getColumnPhpType($this->type);
$this->resolveColumnDefault($c, $column['dflt_value']); $this->resolveColumnDefault($c, $column['dflt_value']);

8
framework/validators/UniqueValidator.php

@ -18,11 +18,6 @@ namespace yii\validators;
class UniqueValidator extends Validator 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, * @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. * meaning that if the attribute is empty, it is considered valid.
*/ */
@ -76,8 +71,7 @@ class UniqueValidator extends Validator
} }
$query = $className::find(); $query = $className::find();
$query->where($this->caseSensitive ? "{$column->quotedName}=:value" : "LOWER({$column->quotedName})=LOWER(:value)"); $query->where(array($column->name => $value));
$query->params(array(':value' => $value));
if ($object->getIsNewRecord()) { if ($object->getIsNewRecord()) {
// if current $object isn't in the database yet then it's OK just // if current $object isn't in the database yet then it's OK just

Loading…
Cancel
Save