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

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

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

w  
Qiang Xue committed
15
/**
Qiang Xue committed
16
 * Driver 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 Driver extends \yii\db\dao\Driver
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 82 83 84
	 * @return QueryBuilder query builder instance
	 */
	public function createQueryBuilder()
	{
		return new QueryBuilder($this->connection);
	}

w  
Qiang Xue committed
85 86 87
	/**
	 * Loads the metadata for the specified table.
	 * @param string $name table name
Qiang Xue committed
88
	 * @return \yii\db\dao\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 98 99 100 101
			$this->findConstraints($table);
			return $table;
		}
	}

	/**
Qiang Xue committed
102 103 104
	 * 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
105 106 107 108
	 */
	protected function resolveTableNames($table, $name)
	{
		$parts = explode('.', str_replace('`', '', $name));
w  
Qiang Xue committed
109
		if (isset($parts[1])) {
w  
Qiang Xue committed
110 111
			$table->schemaName = $parts[0];
			$table->name = $parts[1];
w  
Qiang Xue committed
112
			$table->quotedName = $this->quoteSimpleTableName($table->schemaName) . '.' . $this->quoteSimpleTableName($table->name);
Qiang Xue committed
113
		} else {
w  
Qiang Xue committed
114
			$table->name = $parts[0];
w  
Qiang Xue committed
115
			$table->quotedName = $this->quoteSimpleTableName($table->name);
w  
Qiang Xue committed
116 117 118 119 120 121
		}
	}

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

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

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

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

		return $c;
	}

Qiang Xue committed
143
	/**
Qiang Xue committed
144 145 146
	 * 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
147
	 */
Qiang Xue committed
148
	protected function resolveColumnDefault($column, $value)
Qiang Xue committed
149 150 151 152 153 154 155
	{
		if ($column->type !== 'timestamp' || $value !== 'CURRENT_TIMESTAMP') {
			$column->defaultValue = $column->typecast($value);
		}
	}

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

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

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

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