Schema.php 6.98 KB
Newer Older
w  
Qiang Xue committed
1 2
<?php
/**
Qiang Xue committed
3
 * Schema class file.
w  
Qiang Xue committed
4 5
 *
 * @link http://www.yiiframework.com/
Qiang Xue committed
6
 * @copyright Copyright &copy; 2008 Yii Software LLC
w  
Qiang Xue committed
7 8 9
 * @license http://www.yiiframework.com/license/
 */

Qiang Xue committed
10
namespace yii\db\mysql;
w  
Qiang Xue committed
11

Qiang Xue committed
12 13
use yii\db\TableSchema;
use yii\db\ColumnSchema;
Qiang Xue committed
14

w  
Qiang Xue committed
15
/**
Qiang Xue committed
16
 * Schema is the class for retrieving metadata from a MySQL database (version 4.1.x and 5.x).
w  
Qiang Xue committed
17 18
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
w  
Qiang Xue committed
19
 * @since 2.0
w  
Qiang Xue committed
20
 */
Qiang Xue committed
21
class Schema extends \yii\db\Schema
w  
Qiang Xue committed
22
{
Qiang Xue committed
23
	/**
Qiang Xue committed
24
	 * @var array mapping from physical column types (keys) to abstract column types (values)
Qiang Xue committed
25
	 */
Qiang Xue committed
26
	public $typeMap = array(
Qiang Xue committed
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
		'tinyint' => self::TYPE_SMALLINT,
		'bit' => self::TYPE_SMALLINT,
		'smallint' => self::TYPE_SMALLINT,
		'mediumint' => self::TYPE_INTEGER,
		'int' => self::TYPE_INTEGER,
		'integer' => self::TYPE_INTEGER,
		'bigint' => self::TYPE_BIGINT,
		'float' => self::TYPE_FLOAT,
		'double' => self::TYPE_FLOAT,
		'real' => self::TYPE_FLOAT,
		'decimal' => self::TYPE_DECIMAL,
		'numeric' => self::TYPE_DECIMAL,
		'tinytext' => self::TYPE_TEXT,
		'mediumtext' => self::TYPE_TEXT,
		'longtext' => self::TYPE_TEXT,
		'text' => self::TYPE_TEXT,
		'varchar' => self::TYPE_STRING,
		'string' => self::TYPE_STRING,
		'char' => self::TYPE_STRING,
		'datetime' => self::TYPE_DATETIME,
		'year' => self::TYPE_DATE,
		'date' => self::TYPE_DATE,
		'time' => self::TYPE_TIME,
		'timestamp' => self::TYPE_TIMESTAMP,
		'enum' => self::TYPE_STRING,
	);

w  
Qiang Xue committed
54 55
	/**
	 * Quotes a table name for use in a query.
Qiang Xue committed
56
	 * A simple table name has no schema prefix.
w  
Qiang Xue committed
57 58 59 60 61
	 * @param string $name table name
	 * @return string the properly quoted table name
	 */
	public function quoteSimpleTableName($name)
	{
w  
Qiang Xue committed
62
		return strpos($name, "`") !== false ? $name : "`" . $name . "`";
w  
Qiang Xue committed
63 64 65 66
	}

	/**
	 * Quotes a column name for use in a query.
Qiang Xue committed
67
	 * A simple column name has no prefix.
w  
Qiang Xue committed
68 69 70 71 72
	 * @param string $name column name
	 * @return string the properly quoted column name
	 */
	public function quoteSimpleColumnName($name)
	{
w  
Qiang Xue committed
73
		return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`';
w  
Qiang Xue committed
74 75
	}

Qiang Xue committed
76
	/**
Qiang Xue committed
77
	 * Creates a query builder for the MySQL database.
Qiang Xue committed
78 79 80 81
	 * @return QueryBuilder query builder instance
	 */
	public function createQueryBuilder()
	{
Qiang Xue committed
82
		return new QueryBuilder($this->db);
Qiang Xue committed
83 84
	}

w  
Qiang Xue committed
85 86 87
	/**
	 * Loads the metadata for the specified table.
	 * @param string $name table name
Qiang Xue committed
88
	 * @return TableSchema driver dependent table metadata. Null if the table does not exist.
w  
Qiang Xue committed
89
	 */
w  
Qiang Xue committed
90
	protected function loadTableSchema($name)
w  
Qiang Xue committed
91
	{
w  
Qiang Xue committed
92
		$table = new TableSchema;
w  
Qiang Xue committed
93 94
		$this->resolveTableNames($table, $name);

w  
Qiang Xue committed
95
		if ($this->findColumns($table)) {
w  
Qiang Xue committed
96 97
			$this->findConstraints($table);
			return $table;
Qiang Xue committed
98 99
		} else {
			return null;
w  
Qiang Xue committed
100 101 102 103
		}
	}

	/**
Qiang Xue committed
104
	 * Resolves the table name and schema name (if any).
Qiang Xue committed
105
	 * @param TableSchema $table the table metadata object
Qiang Xue committed
106
	 * @param string $name the table name
w  
Qiang Xue committed
107 108 109 110
	 */
	protected function resolveTableNames($table, $name)
	{
		$parts = explode('.', str_replace('`', '', $name));
w  
Qiang Xue committed
111
		if (isset($parts[1])) {
w  
Qiang Xue committed
112 113
			$table->schemaName = $parts[0];
			$table->name = $parts[1];
Qiang Xue committed
114
		} else {
w  
Qiang Xue committed
115 116 117 118 119
			$table->name = $parts[0];
		}
	}

	/**
Qiang Xue committed
120 121 122
	 * Loads the column information into a [[ColumnSchema]] object.
	 * @param array $info column information
	 * @return ColumnSchema the column schema object
w  
Qiang Xue committed
123
	 */
Qiang Xue committed
124
	protected function loadColumnSchema($info)
w  
Qiang Xue committed
125
	{
Qiang Xue committed
126
		$column = new ColumnSchema;
w  
Qiang Xue committed
127

Qiang Xue committed
128 129 130 131
		$column->name = $info['Field'];
		$column->allowNull = $info['Null'] === 'YES';
		$column->isPrimaryKey = strpos($info['Key'], 'PRI') !== false;
		$column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false;
Qiang Xue committed
132 133
		$column->comment = $info['Comment'];

Qiang Xue committed
134

Qiang Xue committed
135
		$column->dbType = $info['Type'];
Qiang Xue committed
136 137
		$column->unsigned = strpos($column->dbType, 'unsigned') !== false;

Qiang Xue committed
138
		$column->type = self::TYPE_STRING;
Qiang Xue committed
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
		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]);
					foreach ($values as $i => $value) {
						$values[$i] = trim($value, "'");
					}
					$column->enumValues = $values;
				} else {
					$values = explode(',', $matches[2]);
					$column->size = $column->precision = (int)$values[0];
					if (isset($values[1])) {
						$column->scale = (int)$values[1];
					}
					if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) {
						$column->type = 'boolean';
					} elseif ($type === 'bit') {
						if ($column->size > 32) {
							$column->type = 'bigint';
						} elseif ($column->size === 32) {
							$column->type = 'integer';
						}
					}
				}
			}
		}
Qiang Xue committed
169

170
		$column->phpType = $this->getColumnPhpType($column);
Qiang Xue committed
171 172 173 174 175 176

		if ($column->type !== 'timestamp' || $info['Default'] !== 'CURRENT_TIMESTAMP') {
			$column->defaultValue = $column->typecast($info['Default']);
		}

		return $column;
Qiang Xue committed
177 178
	}

w  
Qiang Xue committed
179
	/**
Qiang Xue committed
180
	 * Collects the metadata of table columns.
Qiang Xue committed
181
	 * @param TableSchema $table the table metadata
w  
Qiang Xue committed
182
	 * @return boolean whether the table exists in the database
w  
Qiang Xue committed
183
	 */
w  
Qiang Xue committed
184
	protected function findColumns($table)
w  
Qiang Xue committed
185
	{
Qiang Xue committed
186
		$sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteSimpleTableName($table->name);
w  
Qiang Xue committed
187
		try {
Qiang Xue committed
188
			$columns = $this->db->createCommand($sql)->queryAll();
Qiang Xue committed
189
		} catch (\Exception $e) {
w  
Qiang Xue committed
190 191
			return false;
		}
Qiang Xue committed
192
		foreach ($columns as $info) {
Qiang Xue committed
193
			$column = $this->loadColumnSchema($info);
Qiang Xue committed
194 195
			$table->columns[$column->name] = $column;
			if ($column->isPrimaryKey) {
Qiang Xue committed
196
				$table->primaryKey[] = $column->name;
Qiang Xue committed
197
				if ($column->autoIncrement) {
w  
Qiang Xue committed
198 199 200 201 202
					$table->sequenceName = '';
				}
			}
		}
		return true;
w  
Qiang Xue committed
203 204 205 206
	}

	/**
	 * Collects the foreign key column details for the given table.
Qiang Xue committed
207
	 * @param TableSchema $table the table metadata
w  
Qiang Xue committed
208 209 210
	 */
	protected function findConstraints($table)
	{
Qiang Xue committed
211
		$row = $this->db->createCommand('SHOW CREATE TABLE ' . $this->quoteSimpleTableName($table->name))->queryRow();
Qiang Xue committed
212 213 214 215 216 217 218
		if (isset($row['Create Table'])) {
			$sql = $row['Create Table'];
		} else {
			$row = array_values($row);
			$sql = $row[1];
		}

w  
Qiang Xue committed
219
		$regexp = '/FOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+([^\(^\s]+)\s*\(([^\)]+)\)/mi';
Qiang Xue committed
220 221 222 223 224 225 226
		if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) {
			foreach ($matches as $match) {
				$fks = array_map('trim', explode(',', str_replace('`', '', $match[1])));
				$pks = array_map('trim', explode(',', str_replace('`', '', $match[3])));
				$constraint = array(str_replace('`', '', $match[2]));
				foreach ($fks as $k => $name) {
					$constraint[$name] = $pks[$k];
w  
Qiang Xue committed
227
				}
Qiang Xue committed
228
				$table->foreignKeys[] = $constraint;
w  
Qiang Xue committed
229 230 231 232 233 234
			}
		}
	}

	/**
	 * Returns all table names in the database.
Qiang Xue committed
235 236
	 * This method should be overridden by child classes in order to support this feature
	 * because the default implementation simply throws an exception.
w  
Qiang Xue committed
237
	 * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
Qiang Xue committed
238
	 * @return array all table names in the database. The names have NO the schema name prefix.
w  
Qiang Xue committed
239 240 241
	 */
	protected function findTableNames($schema = '')
	{
Qiang Xue committed
242 243 244
		$sql = 'SHOW TABLES';
		if ($schema !== '') {
			$sql .= ' FROM ' . $this->quoteSimpleTableName($schema);
w  
Qiang Xue committed
245
		}
Qiang Xue committed
246
		return $this->db->createCommand($sql)->queryColumn();
w  
Qiang Xue committed
247 248
	}
}