1 | <?php |
||
2 | /** |
||
3 | * @link https://www.yiiframework.com/ |
||
4 | * @copyright Copyright (c) 2008 Yii Software LLC |
||
5 | * @license https://www.yiiframework.com/license/ |
||
6 | */ |
||
7 | |||
8 | namespace yii\db\mysql; |
||
9 | |||
10 | use Yii; |
||
11 | use yii\base\InvalidConfigException; |
||
12 | use yii\base\NotSupportedException; |
||
13 | use yii\db\CheckConstraint; |
||
14 | use yii\db\Constraint; |
||
15 | use yii\db\ConstraintFinderInterface; |
||
16 | use yii\db\ConstraintFinderTrait; |
||
17 | use yii\db\Exception; |
||
18 | use yii\db\Expression; |
||
19 | use yii\db\ForeignKeyConstraint; |
||
20 | use yii\db\IndexConstraint; |
||
21 | use yii\db\TableSchema; |
||
22 | use yii\helpers\ArrayHelper; |
||
23 | |||
24 | /** |
||
25 | * Schema is the class for retrieving metadata from a MySQL database (version 4.1.x and 5.x). |
||
26 | * |
||
27 | * @author Qiang Xue <[email protected]> |
||
28 | * @since 2.0 |
||
29 | */ |
||
30 | class Schema extends \yii\db\Schema implements ConstraintFinderInterface |
||
31 | { |
||
32 | use ConstraintFinderTrait; |
||
33 | |||
34 | /** |
||
35 | * {@inheritdoc} |
||
36 | */ |
||
37 | public $columnSchemaClass = 'yii\db\mysql\ColumnSchema'; |
||
38 | /** |
||
39 | * @var bool whether MySQL used is older than 5.1. |
||
40 | */ |
||
41 | private $_oldMysql; |
||
42 | |||
43 | |||
44 | /** |
||
45 | * @var array mapping from physical column types (keys) to abstract column types (values) |
||
46 | */ |
||
47 | public $typeMap = [ |
||
48 | 'tinyint' => self::TYPE_TINYINT, |
||
49 | 'bool' => self::TYPE_TINYINT, |
||
50 | 'boolean' => self::TYPE_TINYINT, |
||
51 | 'bit' => self::TYPE_INTEGER, |
||
52 | 'smallint' => self::TYPE_SMALLINT, |
||
53 | 'mediumint' => self::TYPE_INTEGER, |
||
54 | 'int' => self::TYPE_INTEGER, |
||
55 | 'integer' => self::TYPE_INTEGER, |
||
56 | 'bigint' => self::TYPE_BIGINT, |
||
57 | 'float' => self::TYPE_FLOAT, |
||
58 | 'double' => self::TYPE_DOUBLE, |
||
59 | 'double precision' => self::TYPE_DOUBLE, |
||
60 | 'real' => self::TYPE_FLOAT, |
||
61 | 'decimal' => self::TYPE_DECIMAL, |
||
62 | 'numeric' => self::TYPE_DECIMAL, |
||
63 | 'dec' => self::TYPE_DECIMAL, |
||
64 | 'fixed' => self::TYPE_DECIMAL, |
||
65 | 'tinytext' => self::TYPE_TEXT, |
||
66 | 'mediumtext' => self::TYPE_TEXT, |
||
67 | 'longtext' => self::TYPE_TEXT, |
||
68 | 'longblob' => self::TYPE_BINARY, |
||
69 | 'blob' => self::TYPE_BINARY, |
||
70 | 'text' => self::TYPE_TEXT, |
||
71 | 'varchar' => self::TYPE_STRING, |
||
72 | 'string' => self::TYPE_STRING, |
||
73 | 'char' => self::TYPE_CHAR, |
||
74 | 'datetime' => self::TYPE_DATETIME, |
||
75 | 'year' => self::TYPE_DATE, |
||
76 | 'date' => self::TYPE_DATE, |
||
77 | 'time' => self::TYPE_TIME, |
||
78 | 'timestamp' => self::TYPE_TIMESTAMP, |
||
79 | 'enum' => self::TYPE_STRING, |
||
80 | 'set' => self::TYPE_STRING, |
||
81 | 'binary' => self::TYPE_BINARY, |
||
82 | 'varbinary' => self::TYPE_BINARY, |
||
83 | 'json' => self::TYPE_JSON, |
||
84 | ]; |
||
85 | |||
86 | /** |
||
87 | * {@inheritdoc} |
||
88 | */ |
||
89 | protected $tableQuoteCharacter = '`'; |
||
90 | /** |
||
91 | * {@inheritdoc} |
||
92 | */ |
||
93 | protected $columnQuoteCharacter = '`'; |
||
94 | |||
95 | /** |
||
96 | * {@inheritdoc} |
||
97 | */ |
||
98 | 81 | protected function resolveTableName($name) |
|
99 | { |
||
100 | 81 | $resolvedName = new TableSchema(); |
|
101 | 81 | $parts = explode('.', str_replace('`', '', $name)); |
|
102 | 81 | if (isset($parts[1])) { |
|
103 | $resolvedName->schemaName = $parts[0]; |
||
104 | $resolvedName->name = $parts[1]; |
||
105 | } else { |
||
106 | 81 | $resolvedName->schemaName = $this->defaultSchema; |
|
107 | 81 | $resolvedName->name = $name; |
|
108 | } |
||
109 | 81 | $resolvedName->fullName = ($resolvedName->schemaName !== $this->defaultSchema ? $resolvedName->schemaName . '.' : '') . $resolvedName->name; |
|
110 | 81 | return $resolvedName; |
|
111 | } |
||
112 | |||
113 | /** |
||
114 | * {@inheritdoc} |
||
115 | */ |
||
116 | 7 | protected function findTableNames($schema = '') |
|
117 | { |
||
118 | 7 | $sql = 'SHOW TABLES'; |
|
119 | 7 | if ($schema !== '') { |
|
120 | $sql .= ' FROM ' . $this->quoteSimpleTableName($schema); |
||
121 | } |
||
122 | |||
123 | 7 | return $this->db->createCommand($sql)->queryColumn(); |
|
124 | } |
||
125 | |||
126 | /** |
||
127 | * {@inheritdoc} |
||
128 | */ |
||
129 | 412 | protected function loadTableSchema($name) |
|
130 | { |
||
131 | 412 | $table = new TableSchema(); |
|
132 | 412 | $this->resolveTableNames($table, $name); |
|
133 | |||
134 | 412 | if ($this->findColumns($table)) { |
|
135 | 407 | $this->findConstraints($table); |
|
136 | 407 | return $table; |
|
137 | } |
||
138 | |||
139 | 16 | return null; |
|
140 | } |
||
141 | |||
142 | /** |
||
143 | * {@inheritdoc} |
||
144 | */ |
||
145 | 54 | protected function loadTablePrimaryKey($tableName) |
|
146 | { |
||
147 | 54 | return $this->loadTableConstraints($tableName, 'primaryKey'); |
|
148 | } |
||
149 | |||
150 | /** |
||
151 | * {@inheritdoc} |
||
152 | */ |
||
153 | 4 | protected function loadTableForeignKeys($tableName) |
|
154 | { |
||
155 | 4 | return $this->loadTableConstraints($tableName, 'foreignKeys'); |
|
156 | } |
||
157 | |||
158 | /** |
||
159 | * {@inheritdoc} |
||
160 | */ |
||
161 | 51 | protected function loadTableIndexes($tableName) |
|
162 | { |
||
163 | 51 | static $sql = <<<'SQL' |
|
164 | SELECT |
||
165 | `s`.`INDEX_NAME` AS `name`, |
||
166 | `s`.`COLUMN_NAME` AS `column_name`, |
||
167 | `s`.`NON_UNIQUE` ^ 1 AS `index_is_unique`, |
||
168 | `s`.`INDEX_NAME` = 'PRIMARY' AS `index_is_primary` |
||
169 | FROM `information_schema`.`STATISTICS` AS `s` |
||
170 | WHERE `s`.`TABLE_SCHEMA` = COALESCE(:schemaName, DATABASE()) AND `s`.`INDEX_SCHEMA` = `s`.`TABLE_SCHEMA` AND `s`.`TABLE_NAME` = :tableName |
||
171 | ORDER BY `s`.`SEQ_IN_INDEX` ASC |
||
172 | 51 | SQL; |
|
173 | |||
174 | 51 | $resolvedName = $this->resolveTableName($tableName); |
|
175 | 51 | $indexes = $this->db->createCommand($sql, [ |
|
176 | 51 | ':schemaName' => $resolvedName->schemaName, |
|
177 | 51 | ':tableName' => $resolvedName->name, |
|
178 | 51 | ])->queryAll(); |
|
179 | 51 | $indexes = $this->normalizePdoRowKeyCase($indexes, true); |
|
180 | 51 | $indexes = ArrayHelper::index($indexes, null, 'name'); |
|
181 | 51 | $result = []; |
|
182 | 51 | foreach ($indexes as $name => $index) { |
|
183 | 51 | $result[] = new IndexConstraint([ |
|
184 | 51 | 'isPrimary' => (bool) $index[0]['index_is_primary'], |
|
185 | 51 | 'isUnique' => (bool) $index[0]['index_is_unique'], |
|
186 | 51 | 'name' => $name !== 'PRIMARY' ? $name : null, |
|
187 | 51 | 'columnNames' => ArrayHelper::getColumn($index, 'column_name'), |
|
188 | 51 | ]); |
|
189 | } |
||
190 | |||
191 | 51 | return $result; |
|
192 | } |
||
193 | |||
194 | /** |
||
195 | * {@inheritdoc} |
||
196 | */ |
||
197 | 13 | protected function loadTableUniques($tableName) |
|
198 | { |
||
199 | 13 | return $this->loadTableConstraints($tableName, 'uniques'); |
|
200 | } |
||
201 | |||
202 | /** |
||
203 | * {@inheritdoc} |
||
204 | */ |
||
205 | 12 | protected function loadTableChecks($tableName) |
|
206 | { |
||
207 | 12 | $version = $this->db->getServerVersion(); |
|
208 | |||
209 | // check version MySQL >= 8.0.16 |
||
210 | 12 | if (\stripos($version, 'MariaDb') === false && \version_compare($version, '8.0.16', '<')) { |
|
211 | 12 | throw new NotSupportedException('MySQL < 8.0.16 does not support check constraints.'); |
|
212 | } |
||
213 | |||
214 | $checks = []; |
||
215 | |||
216 | $sql = <<<SQL |
||
217 | SELECT cc.CONSTRAINT_NAME as constraint_name, cc.CHECK_CLAUSE as check_clause |
||
218 | FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc |
||
219 | JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS cc |
||
220 | ON tc.CONSTRAINT_NAME = cc.CONSTRAINT_NAME |
||
221 | WHERE tc.TABLE_NAME = :tableName AND tc.CONSTRAINT_TYPE = 'CHECK'; |
||
222 | SQL; |
||
223 | |||
224 | $resolvedName = $this->resolveTableName($tableName); |
||
225 | $tableRows = $this->db->createCommand($sql, [':tableName' => $resolvedName->name])->queryAll(); |
||
226 | |||
227 | if ($tableRows === []) { |
||
228 | return $checks; |
||
229 | } |
||
230 | |||
231 | $tableRows = $this->normalizePdoRowKeyCase($tableRows, true); |
||
232 | |||
233 | foreach ($tableRows as $tableRow) { |
||
234 | $check = new CheckConstraint( |
||
235 | [ |
||
236 | 'name' => $tableRow['constraint_name'], |
||
237 | 'expression' => $tableRow['check_clause'], |
||
238 | ] |
||
239 | ); |
||
240 | $checks[] = $check; |
||
241 | } |
||
242 | |||
243 | return $checks; |
||
244 | } |
||
245 | |||
246 | /** |
||
247 | * {@inheritdoc} |
||
248 | * @throws NotSupportedException if this method is called. |
||
249 | */ |
||
250 | 12 | protected function loadTableDefaultValues($tableName) |
|
0 ignored issues
–
show
|
|||
251 | { |
||
252 | 12 | throw new NotSupportedException('MySQL does not support default value constraints.'); |
|
253 | } |
||
254 | |||
255 | /** |
||
256 | * Creates a query builder for the MySQL database. |
||
257 | * @return QueryBuilder query builder instance |
||
258 | */ |
||
259 | 406 | public function createQueryBuilder() |
|
260 | { |
||
261 | 406 | return Yii::createObject(QueryBuilder::className(), [$this->db]); |
|
262 | } |
||
263 | |||
264 | /** |
||
265 | * Resolves the table name and schema name (if any). |
||
266 | * @param TableSchema $table the table metadata object |
||
267 | * @param string $name the table name |
||
268 | */ |
||
269 | 412 | protected function resolveTableNames($table, $name) |
|
270 | { |
||
271 | 412 | $parts = explode('.', str_replace('`', '', $name)); |
|
272 | 412 | if (isset($parts[1])) { |
|
273 | $table->schemaName = $parts[0]; |
||
274 | $table->name = $parts[1]; |
||
275 | $table->fullName = $table->schemaName . '.' . $table->name; |
||
276 | } else { |
||
277 | 412 | $table->fullName = $table->name = $parts[0]; |
|
278 | } |
||
279 | } |
||
280 | |||
281 | /** |
||
282 | * Loads the column information into a [[ColumnSchema]] object. |
||
283 | * @param array $info column information |
||
284 | * @return ColumnSchema the column schema object |
||
285 | */ |
||
286 | 409 | protected function loadColumnSchema($info) |
|
287 | { |
||
288 | 409 | $column = $this->createColumnSchema(); |
|
289 | |||
290 | 409 | $column->name = $info['field']; |
|
291 | 409 | $column->allowNull = $info['null'] === 'YES'; |
|
292 | 409 | $column->isPrimaryKey = strpos($info['key'], 'PRI') !== false; |
|
293 | 409 | $column->autoIncrement = stripos($info['extra'], 'auto_increment') !== false; |
|
294 | 409 | $column->comment = $info['comment']; |
|
295 | |||
296 | 409 | $column->dbType = $info['type']; |
|
297 | 409 | $column->unsigned = stripos($column->dbType, 'unsigned') !== false; |
|
298 | |||
299 | 409 | $column->type = self::TYPE_STRING; |
|
300 | 409 | if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) { |
|
301 | 409 | $type = strtolower($matches[1]); |
|
302 | 409 | if (isset($this->typeMap[$type])) { |
|
303 | 409 | $column->type = $this->typeMap[$type]; |
|
304 | } |
||
305 | 409 | if (!empty($matches[2])) { |
|
306 | 407 | if ($type === 'enum') { |
|
307 | 28 | preg_match_all("/'[^']*'/", $matches[2], $values); |
|
308 | 28 | foreach ($values[0] as $i => $value) { |
|
309 | 28 | $values[$i] = trim($value, "'"); |
|
310 | } |
||
311 | 28 | $column->enumValues = $values; |
|
312 | } else { |
||
313 | 407 | $values = explode(',', $matches[2]); |
|
314 | 407 | $column->size = $column->precision = (int) $values[0]; |
|
315 | 407 | if (isset($values[1])) { |
|
316 | 106 | $column->scale = (int) $values[1]; |
|
317 | } |
||
318 | 407 | if ($column->size === 1 && $type === 'bit') { |
|
319 | 6 | $column->type = 'boolean'; |
|
320 | 407 | } elseif ($type === 'bit') { |
|
321 | 28 | if ($column->size > 32) { |
|
322 | $column->type = 'bigint'; |
||
323 | 28 | } elseif ($column->size === 32) { |
|
324 | $column->type = 'integer'; |
||
325 | } |
||
326 | } |
||
327 | } |
||
328 | } |
||
329 | } |
||
330 | |||
331 | 409 | $column->phpType = $this->getColumnPhpType($column); |
|
332 | |||
333 | 409 | if (!$column->isPrimaryKey) { |
|
334 | /** |
||
335 | * When displayed in the INFORMATION_SCHEMA.COLUMNS table, a default CURRENT TIMESTAMP is displayed |
||
336 | * as CURRENT_TIMESTAMP up until MariaDB 10.2.2, and as current_timestamp() from MariaDB 10.2.3. |
||
337 | * |
||
338 | * See details here: https://mariadb.com/kb/en/library/now/#description |
||
339 | */ |
||
340 | if ( |
||
341 | 404 | in_array($column->type, ['timestamp', 'datetime', 'date', 'time']) |
|
342 | 404 | && isset($info['default']) |
|
343 | 404 | && preg_match('/^current_timestamp(?:\(([0-9]*)\))?$/i', $info['default'], $matches) |
|
344 | ) { |
||
345 | 31 | $column->defaultValue = new Expression('CURRENT_TIMESTAMP' . (!empty($matches[1]) ? '(' . $matches[1] . ')' : '')); |
|
346 | 401 | } elseif (isset($type) && $type === 'bit') { |
|
347 | 29 | $column->defaultValue = bindec(trim(isset($info['default']) ? $info['default'] : '', 'b\'')); |
|
348 | } else { |
||
349 | 400 | $column->defaultValue = $column->phpTypecast($info['default']); |
|
350 | } |
||
351 | } |
||
352 | |||
353 | 409 | return $column; |
|
354 | } |
||
355 | |||
356 | /** |
||
357 | * Collects the metadata of table columns. |
||
358 | * @param TableSchema $table the table metadata |
||
359 | * @return bool whether the table exists in the database |
||
360 | * @throws \Exception if DB query fails |
||
361 | */ |
||
362 | 412 | protected function findColumns($table) |
|
363 | { |
||
364 | 412 | $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteTableName($table->fullName); |
|
365 | try { |
||
366 | 412 | $columns = $this->db->createCommand($sql)->queryAll(); |
|
367 | 16 | } catch (\Exception $e) { |
|
368 | 16 | $previous = $e->getPrevious(); |
|
369 | 16 | if ($previous instanceof \PDOException && strpos($previous->getMessage(), 'SQLSTATE[42S02') !== false) { |
|
370 | // table does not exist |
||
371 | // https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html#error_er_bad_table_error |
||
372 | 16 | return false; |
|
373 | } |
||
374 | throw $e; |
||
375 | } |
||
376 | |||
377 | |||
378 | 407 | $jsonColumns = $this->getJsonColumns($table); |
|
379 | |||
380 | 407 | foreach ($columns as $info) { |
|
381 | 407 | if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) !== \PDO::CASE_LOWER) { |
|
382 | 406 | $info = array_change_key_case($info, CASE_LOWER); |
|
383 | } |
||
384 | |||
385 | 407 | if (\in_array($info['field'], $jsonColumns, true)) { |
|
386 | $info['type'] = static::TYPE_JSON; |
||
387 | } |
||
388 | |||
389 | 407 | $column = $this->loadColumnSchema($info); |
|
390 | 407 | $table->columns[$column->name] = $column; |
|
391 | 407 | if ($column->isPrimaryKey) { |
|
392 | 379 | $table->primaryKey[] = $column->name; |
|
393 | 379 | if ($column->autoIncrement) { |
|
394 | 251 | $table->sequenceName = ''; |
|
395 | } |
||
396 | } |
||
397 | } |
||
398 | |||
399 | 407 | return true; |
|
400 | } |
||
401 | |||
402 | /** |
||
403 | * Gets the CREATE TABLE sql string. |
||
404 | * @param TableSchema $table the table metadata |
||
405 | * @return string $sql the result of 'SHOW CREATE TABLE' |
||
406 | */ |
||
407 | 407 | protected function getCreateTableSql($table) |
|
408 | { |
||
409 | 407 | $row = $this->db->createCommand('SHOW CREATE TABLE ' . $this->quoteTableName($table->fullName))->queryOne(); |
|
410 | 407 | if (isset($row['Create Table'])) { |
|
411 | 405 | $sql = $row['Create Table']; |
|
412 | } else { |
||
413 | 6 | $row = array_values($row); |
|
414 | 6 | $sql = $row[1]; |
|
415 | } |
||
416 | |||
417 | 407 | return $sql; |
|
418 | } |
||
419 | |||
420 | /** |
||
421 | * Collects the foreign key column details for the given table. |
||
422 | * @param TableSchema $table the table metadata |
||
423 | * @throws \Exception |
||
424 | */ |
||
425 | 407 | protected function findConstraints($table) |
|
426 | { |
||
427 | 407 | $sql = <<<'SQL' |
|
428 | SELECT |
||
429 | `kcu`.`CONSTRAINT_NAME` AS `constraint_name`, |
||
430 | `kcu`.`COLUMN_NAME` AS `column_name`, |
||
431 | `kcu`.`REFERENCED_TABLE_NAME` AS `referenced_table_name`, |
||
432 | `kcu`.`REFERENCED_COLUMN_NAME` AS `referenced_column_name` |
||
433 | FROM `information_schema`.`REFERENTIAL_CONSTRAINTS` AS `rc` |
||
434 | JOIN `information_schema`.`KEY_COLUMN_USAGE` AS `kcu` ON |
||
435 | ( |
||
436 | `kcu`.`CONSTRAINT_CATALOG` = `rc`.`CONSTRAINT_CATALOG` OR |
||
437 | (`kcu`.`CONSTRAINT_CATALOG` IS NULL AND `rc`.`CONSTRAINT_CATALOG` IS NULL) |
||
438 | ) AND |
||
439 | `kcu`.`CONSTRAINT_SCHEMA` = `rc`.`CONSTRAINT_SCHEMA` AND |
||
440 | `kcu`.`CONSTRAINT_NAME` = `rc`.`CONSTRAINT_NAME` |
||
441 | WHERE `rc`.`CONSTRAINT_SCHEMA` = database() AND `kcu`.`TABLE_SCHEMA` = database() |
||
442 | AND `rc`.`TABLE_NAME` = :tableName AND `kcu`.`TABLE_NAME` = :tableName1 |
||
443 | 407 | SQL; |
|
444 | |||
445 | try { |
||
446 | 407 | $rows = $this->db->createCommand($sql, [':tableName' => $table->name, ':tableName1' => $table->name])->queryAll(); |
|
447 | 407 | $constraints = []; |
|
448 | |||
449 | 407 | foreach ($rows as $row) { |
|
450 | 273 | $constraints[$row['constraint_name']]['referenced_table_name'] = $row['referenced_table_name']; |
|
451 | 273 | $constraints[$row['constraint_name']]['columns'][$row['column_name']] = $row['referenced_column_name']; |
|
452 | } |
||
453 | |||
454 | 407 | $table->foreignKeys = []; |
|
455 | 407 | foreach ($constraints as $name => $constraint) { |
|
456 | 273 | $table->foreignKeys[$name] = array_merge( |
|
457 | 273 | [$constraint['referenced_table_name']], |
|
458 | 273 | $constraint['columns'] |
|
459 | 273 | ); |
|
460 | } |
||
461 | } catch (\Exception $e) { |
||
462 | $previous = $e->getPrevious(); |
||
463 | if (!$previous instanceof \PDOException || strpos($previous->getMessage(), 'SQLSTATE[42S02') === false) { |
||
464 | throw $e; |
||
465 | } |
||
466 | |||
467 | // table does not exist, try to determine the foreign keys using the table creation sql |
||
468 | $sql = $this->getCreateTableSql($table); |
||
469 | $regexp = '/FOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+([^\(^\s]+)\s*\(([^\)]+)\)/mi'; |
||
470 | if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) { |
||
471 | foreach ($matches as $match) { |
||
472 | $fks = array_map('trim', explode(',', str_replace(['`', '"'], '', $match[1]))); |
||
473 | $pks = array_map('trim', explode(',', str_replace(['`', '"'], '', $match[3]))); |
||
474 | $constraint = [str_replace(['`', '"'], '', $match[2])]; |
||
475 | foreach ($fks as $k => $name) { |
||
476 | $constraint[$name] = $pks[$k]; |
||
477 | } |
||
478 | $table->foreignKeys[md5(serialize($constraint))] = $constraint; |
||
479 | } |
||
480 | $table->foreignKeys = array_values($table->foreignKeys); |
||
481 | } |
||
482 | } |
||
483 | } |
||
484 | |||
485 | /** |
||
486 | * Returns all unique indexes for the given table. |
||
487 | * |
||
488 | * Each array element is of the following structure: |
||
489 | * |
||
490 | * ```php |
||
491 | * [ |
||
492 | * 'IndexName1' => ['col1' [, ...]], |
||
493 | * 'IndexName2' => ['col2' [, ...]], |
||
494 | * ] |
||
495 | * ``` |
||
496 | * |
||
497 | * @param TableSchema $table the table metadata |
||
498 | * @return array all unique indexes for the given table. |
||
499 | */ |
||
500 | 1 | public function findUniqueIndexes($table) |
|
501 | { |
||
502 | 1 | $sql = $this->getCreateTableSql($table); |
|
503 | 1 | $uniqueIndexes = []; |
|
504 | |||
505 | 1 | $regexp = '/UNIQUE KEY\s+[`"](.+)[`"]\s*\(([`"].+[`"])+\)/mi'; |
|
506 | 1 | if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) { |
|
507 | 1 | foreach ($matches as $match) { |
|
508 | 1 | $indexName = $match[1]; |
|
509 | 1 | $indexColumns = array_map('trim', preg_split('/[`"],[`"]/', trim($match[2], '`"'))); |
|
510 | 1 | $uniqueIndexes[$indexName] = $indexColumns; |
|
511 | } |
||
512 | } |
||
513 | |||
514 | 1 | return $uniqueIndexes; |
|
515 | } |
||
516 | |||
517 | /** |
||
518 | * {@inheritdoc} |
||
519 | */ |
||
520 | 17 | public function createColumnSchemaBuilder($type, $length = null) |
|
521 | { |
||
522 | 17 | return Yii::createObject(ColumnSchemaBuilder::className(), [$type, $length, $this->db]); |
|
523 | } |
||
524 | |||
525 | /** |
||
526 | * @return bool whether the version of the MySQL being used is older than 5.1. |
||
527 | * @throws InvalidConfigException |
||
528 | * @throws Exception |
||
529 | * @since 2.0.13 |
||
530 | */ |
||
531 | protected function isOldMysql() |
||
532 | { |
||
533 | if ($this->_oldMysql === null) { |
||
534 | $version = $this->db->getSlavePdo(true)->getAttribute(\PDO::ATTR_SERVER_VERSION); |
||
535 | $this->_oldMysql = version_compare($version, '5.1', '<='); |
||
0 ignored issues
–
show
It seems like
version_compare($version, '5.1', '<=') can also be of type integer . However, the property $_oldMysql is declared as type boolean . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
Loading history...
|
|||
536 | } |
||
537 | |||
538 | return $this->_oldMysql; |
||
0 ignored issues
–
show
|
|||
539 | } |
||
540 | |||
541 | /** |
||
542 | * Loads multiple types of constraints and returns the specified ones. |
||
543 | * @param string $tableName table name. |
||
544 | * @param string $returnType return type: |
||
545 | * - primaryKey |
||
546 | * - foreignKeys |
||
547 | * - uniques |
||
548 | * @return mixed constraints. |
||
549 | */ |
||
550 | 71 | private function loadTableConstraints($tableName, $returnType) |
|
551 | { |
||
552 | 71 | static $sql = <<<'SQL' |
|
553 | SELECT |
||
554 | `kcu`.`CONSTRAINT_NAME` AS `name`, |
||
555 | `kcu`.`COLUMN_NAME` AS `column_name`, |
||
556 | `tc`.`CONSTRAINT_TYPE` AS `type`, |
||
557 | CASE |
||
558 | WHEN :schemaName IS NULL AND `kcu`.`REFERENCED_TABLE_SCHEMA` = DATABASE() THEN NULL |
||
559 | ELSE `kcu`.`REFERENCED_TABLE_SCHEMA` |
||
560 | END AS `foreign_table_schema`, |
||
561 | `kcu`.`REFERENCED_TABLE_NAME` AS `foreign_table_name`, |
||
562 | `kcu`.`REFERENCED_COLUMN_NAME` AS `foreign_column_name`, |
||
563 | `rc`.`UPDATE_RULE` AS `on_update`, |
||
564 | `rc`.`DELETE_RULE` AS `on_delete`, |
||
565 | `kcu`.`ORDINAL_POSITION` AS `position` |
||
566 | FROM |
||
567 | `information_schema`.`KEY_COLUMN_USAGE` AS `kcu`, |
||
568 | `information_schema`.`REFERENTIAL_CONSTRAINTS` AS `rc`, |
||
569 | `information_schema`.`TABLE_CONSTRAINTS` AS `tc` |
||
570 | WHERE |
||
571 | `kcu`.`TABLE_SCHEMA` = COALESCE(:schemaName1, DATABASE()) AND `kcu`.`CONSTRAINT_SCHEMA` = `kcu`.`TABLE_SCHEMA` AND `kcu`.`TABLE_NAME` = :tableName |
||
572 | AND `rc`.`CONSTRAINT_SCHEMA` = `kcu`.`TABLE_SCHEMA` AND `rc`.`TABLE_NAME` = :tableName1 AND `rc`.`CONSTRAINT_NAME` = `kcu`.`CONSTRAINT_NAME` |
||
573 | AND `tc`.`TABLE_SCHEMA` = `kcu`.`TABLE_SCHEMA` AND `tc`.`TABLE_NAME` = :tableName2 AND `tc`.`CONSTRAINT_NAME` = `kcu`.`CONSTRAINT_NAME` AND `tc`.`CONSTRAINT_TYPE` = 'FOREIGN KEY' |
||
574 | UNION |
||
575 | SELECT |
||
576 | `kcu`.`CONSTRAINT_NAME` AS `name`, |
||
577 | `kcu`.`COLUMN_NAME` AS `column_name`, |
||
578 | `tc`.`CONSTRAINT_TYPE` AS `type`, |
||
579 | NULL AS `foreign_table_schema`, |
||
580 | NULL AS `foreign_table_name`, |
||
581 | NULL AS `foreign_column_name`, |
||
582 | NULL AS `on_update`, |
||
583 | NULL AS `on_delete`, |
||
584 | `kcu`.`ORDINAL_POSITION` AS `position` |
||
585 | FROM |
||
586 | `information_schema`.`KEY_COLUMN_USAGE` AS `kcu`, |
||
587 | `information_schema`.`TABLE_CONSTRAINTS` AS `tc` |
||
588 | WHERE |
||
589 | `kcu`.`TABLE_SCHEMA` = COALESCE(:schemaName2, DATABASE()) AND `kcu`.`TABLE_NAME` = :tableName3 |
||
590 | AND `tc`.`TABLE_SCHEMA` = `kcu`.`TABLE_SCHEMA` AND `tc`.`TABLE_NAME` = :tableName4 AND `tc`.`CONSTRAINT_NAME` = `kcu`.`CONSTRAINT_NAME` AND `tc`.`CONSTRAINT_TYPE` IN ('PRIMARY KEY', 'UNIQUE') |
||
591 | ORDER BY `position` ASC |
||
592 | 71 | SQL; |
|
593 | |||
594 | 71 | $resolvedName = $this->resolveTableName($tableName); |
|
595 | 71 | $constraints = $this->db->createCommand($sql, [ |
|
596 | 71 | ':schemaName' => $resolvedName->schemaName, |
|
597 | 71 | ':schemaName1' => $resolvedName->schemaName, |
|
598 | 71 | ':schemaName2' => $resolvedName->schemaName, |
|
599 | 71 | ':tableName' => $resolvedName->name, |
|
600 | 71 | ':tableName1' => $resolvedName->name, |
|
601 | 71 | ':tableName2' => $resolvedName->name, |
|
602 | 71 | ':tableName3' => $resolvedName->name, |
|
603 | 71 | ':tableName4' => $resolvedName->name |
|
604 | 71 | ])->queryAll(); |
|
605 | 71 | $constraints = $this->normalizePdoRowKeyCase($constraints, true); |
|
606 | 71 | $constraints = ArrayHelper::index($constraints, null, ['type', 'name']); |
|
607 | 71 | $result = [ |
|
608 | 71 | 'primaryKey' => null, |
|
609 | 71 | 'foreignKeys' => [], |
|
610 | 71 | 'uniques' => [], |
|
611 | 71 | ]; |
|
612 | 71 | foreach ($constraints as $type => $names) { |
|
613 | 71 | foreach ($names as $name => $constraint) { |
|
614 | switch ($type) { |
||
615 | 71 | case 'PRIMARY KEY': |
|
616 | 60 | $result['primaryKey'] = new Constraint([ |
|
617 | 60 | 'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'), |
|
618 | 60 | ]); |
|
619 | 60 | break; |
|
620 | 46 | case 'FOREIGN KEY': |
|
621 | 10 | $result['foreignKeys'][] = new ForeignKeyConstraint([ |
|
622 | 10 | 'name' => $name, |
|
623 | 10 | 'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'), |
|
624 | 10 | 'foreignSchemaName' => $constraint[0]['foreign_table_schema'], |
|
625 | 10 | 'foreignTableName' => $constraint[0]['foreign_table_name'], |
|
626 | 10 | 'foreignColumnNames' => ArrayHelper::getColumn($constraint, 'foreign_column_name'), |
|
627 | 10 | 'onDelete' => $constraint[0]['on_delete'], |
|
628 | 10 | 'onUpdate' => $constraint[0]['on_update'], |
|
629 | 10 | ]); |
|
630 | 10 | break; |
|
631 | 37 | case 'UNIQUE': |
|
632 | 37 | $result['uniques'][] = new Constraint([ |
|
633 | 37 | 'name' => $name, |
|
634 | 37 | 'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'), |
|
635 | 37 | ]); |
|
636 | 37 | break; |
|
637 | } |
||
638 | } |
||
639 | } |
||
640 | 71 | foreach ($result as $type => $data) { |
|
641 | 71 | $this->setTableMetadata($tableName, $type, $data); |
|
642 | } |
||
643 | |||
644 | 71 | return $result[$returnType]; |
|
645 | } |
||
646 | |||
647 | 407 | private function getJsonColumns(TableSchema $table): array |
|
648 | { |
||
649 | 407 | $sql = $this->getCreateTableSql($table); |
|
650 | 407 | $result = []; |
|
651 | |||
652 | 407 | $regexp = '/json_valid\([\`"](.+)[\`"]\s*\)/mi'; |
|
653 | |||
654 | 407 | if (\preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) { |
|
655 | foreach ($matches as $match) { |
||
656 | $result[] = $match[1]; |
||
657 | } |
||
658 | } |
||
659 | |||
660 | 407 | return $result; |
|
661 | } |
||
662 | } |
||
663 |
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.