Driver.php 7.7 KB
Newer Older
w  
Qiang Xue committed
1 2
<?php
/**
Qiang Xue committed
3
 * Driver class file.
w  
Qiang Xue committed
4 5 6
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @link http://www.yiiframework.com/
w  
Qiang Xue committed
7
 * @copyright Copyright &copy; 2008-2012 Yii Software LLC
w  
Qiang Xue committed
8 9 10
 * @license http://www.yiiframework.com/license/
 */

w  
Qiang Xue committed
11 12
namespace yii\db\dao\mysql;

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

w  
Qiang Xue committed
16
/**
Qiang Xue committed
17
 * Driver is the class for retrieving metadata from a MySQL database (version 4.1.x and 5.x).
w  
Qiang Xue committed
18 19
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
w  
Qiang Xue committed
20
 * @since 2.0
w  
Qiang Xue committed
21
 */
Qiang Xue committed
22
class Driver extends \yii\db\dao\Driver
w  
Qiang Xue committed
23
{
Qiang Xue committed
24
	/**
Qiang Xue committed
25
	 * @var array mapping from physical column types (keys) to abstract column types (values)
Qiang Xue committed
26
	 */
Qiang Xue committed
27
	public $typeMap = array(
Qiang Xue committed
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 54
		'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
55 56
	/**
	 * Quotes a table name for use in a query.
Qiang Xue committed
57
	 * A simple table name has no schema prefix.
w  
Qiang Xue committed
58 59 60 61 62
	 * @param string $name table name
	 * @return string the properly quoted table name
	 */
	public function quoteSimpleTableName($name)
	{
w  
Qiang Xue committed
63
		return strpos($name, "`") !== false ? $name : "`" . $name . "`";
w  
Qiang Xue committed
64 65 66 67
	}

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

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

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

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

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

	/**
	 * Creates a table column.
	 * @param array $column column metadata
Qiang Xue committed
123
	 * @return ColumnSchema normalized column metadata
w  
Qiang Xue committed
124 125 126
	 */
	protected function createColumn($column)
	{
w  
Qiang Xue committed
127 128
		$c = new ColumnSchema;

w  
Qiang Xue committed
129
		$c->name = $column['Field'];
w  
Qiang Xue committed
130
		$c->quotedName = $this->quoteSimpleColumnName($c->name);
w  
Qiang Xue committed
131 132
		$c->allowNull = $column['Null'] === 'YES';
		$c->isPrimaryKey = strpos($column['Key'], 'PRI') !== false;
w  
Qiang Xue committed
133
		$c->autoIncrement = stripos($column['Extra'], 'auto_increment') !== false;
Qiang Xue committed
134 135 136 137 138

		$c->dbType = $column['Type'];
		$this->resolveColumnType($c);
		$c->resolvePhpType();

Qiang Xue committed
139
		$this->resolveColumnDefault($c, $column['Default']);
w  
Qiang Xue committed
140 141 142 143

		return $c;
	}

Qiang Xue committed
144
	/**
Qiang Xue committed
145 146 147
	 * Resolves the default value for the column.
	 * @param \yii\db\dao\ColumnSchema $column the column metadata object
	 * @param string $value the default value fetched from database
Qiang Xue committed
148
	 */
Qiang Xue committed
149
	protected function resolveColumnDefault($column, $value)
Qiang Xue committed
150 151 152 153 154 155 156
	{
		if ($column->type !== 'timestamp' || $value !== 'CURRENT_TIMESTAMP') {
			$column->defaultValue = $column->typecast($value);
		}
	}

	/**
Qiang Xue committed
157 158
	 * Resolves the abstract data type for the column.
	 * @param \yii\db\dao\ColumnSchema $column the column metadata object
Qiang Xue committed
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
	 */
	public function resolveColumnType($column)
	{
		$column->type = self::TYPE_STRING;
		$column->unsigned = strpos($column->dbType, 'unsigned') !== false;

		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';
						}
					}
				}
			}
		}
	}

w  
Qiang Xue committed
198
	/**
Qiang Xue committed
199
	 * Collects the metadata of table columns.
Qiang Xue committed
200
	 * @param \yii\db\dao\TableSchema $table the table metadata
w  
Qiang Xue committed
201
	 * @return boolean whether the table exists in the database
w  
Qiang Xue committed
202
	 */
w  
Qiang Xue committed
203
	protected function findColumns($table)
w  
Qiang Xue committed
204
	{
w  
Qiang Xue committed
205 206 207
		$sql = 'SHOW COLUMNS FROM ' . $table->quotedName;
		try {
			$columns = $this->connection->createCommand($sql)->queryAll();
Qiang Xue committed
208
		} catch (\Exception $e) {
w  
Qiang Xue committed
209 210 211
			return false;
		}
		foreach ($columns as $column) {
Qiang Xue committed
212 213 214
			$column = $this->createColumn($column);
			$table->columns[$column->name] = $column;
			if ($column->isPrimaryKey) {
Qiang Xue committed
215
				$table->primaryKey[] = $column->name;
Qiang Xue committed
216
				if ($column->autoIncrement) {
w  
Qiang Xue committed
217 218 219 220 221
					$table->sequenceName = '';
				}
			}
		}
		return true;
w  
Qiang Xue committed
222 223 224 225
	}

	/**
	 * Collects the foreign key column details for the given table.
Qiang Xue committed
226
	 * @param \yii\db\dao\TableSchema $table the table metadata
w  
Qiang Xue committed
227 228 229
	 */
	protected function findConstraints($table)
	{
w  
Qiang Xue committed
230
		$row = $this->connection->createCommand('SHOW CREATE TABLE ' . $table->quotedName)->queryRow();
Qiang Xue committed
231 232 233 234 235 236 237
		if (isset($row['Create Table'])) {
			$sql = $row['Create Table'];
		} else {
			$row = array_values($row);
			$sql = $row[1];
		}

w  
Qiang Xue committed
238
		$regexp = '/FOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+([^\(^\s]+)\s*\(([^\)]+)\)/mi';
Qiang Xue committed
239 240 241 242 243 244 245
		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
246
				}
Qiang Xue committed
247
				$table->foreignKeys[] = $constraint;
w  
Qiang Xue committed
248 249 250 251 252 253 254 255 256 257 258 259
			}
		}
	}

	/**
	 * 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.
	 */
	protected function findTableNames($schema = '')
	{
w  
Qiang Xue committed
260 261
		if ($schema === '') {
			return $this->connection->createCommand('SHOW TABLES')->queryColumn();
w  
Qiang Xue committed
262
		}
Qiang Xue committed
263 264
		$sql = 'SHOW TABLES FROM ' . $this->quoteSimpleTableName($schema);
		$names = $this->connection->createCommand($sql)->queryColumn();
Qiang Xue committed
265 266
		foreach ($names as $i => $name) {
			$names[$i] = $schema . '.' . $name;
w  
Qiang Xue committed
267
		}
w  
Qiang Xue committed
268
		return $names;
w  
Qiang Xue committed
269 270
	}
}