GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( ecf3ef...78a151 )
by Robert
11:40
created

Schema::quoteSimpleColumnName()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 2
cts 2
cp 1
cc 3
eloc 2
nc 4
nop 1
crap 3
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db\mysql;
9
10
use yii\base\InvalidConfigException;
11
use yii\base\NotSupportedException;
12
use yii\db\ColumnSchema;
13
use yii\db\Constraint;
14
use yii\db\ConstraintFinderInterface;
15
use yii\db\ConstraintFinderTrait;
16
use yii\db\Exception;
17
use yii\db\Expression;
18
use yii\db\ForeignKeyConstraint;
19
use yii\db\IndexConstraint;
20
use yii\db\TableSchema;
21
use yii\helpers\ArrayHelper;
22
23
/**
24
 * Schema is the class for retrieving metadata from a MySQL database (version 4.1.x and 5.x).
25
 *
26
 * @author Qiang Xue <[email protected]>
27
 * @since 2.0
28
 */
29
class Schema extends \yii\db\Schema implements ConstraintFinderInterface
30
{
31
    use ConstraintFinderTrait;
32
33
    /**
34
     * @var bool whether MySQL used is older than 5.1.
35
     */
36
    private $_oldMysql;
37
38
39
    /**
40
     * @var array mapping from physical column types (keys) to abstract column types (values)
41
     */
42
    public $typeMap = [
43
        'tinyint' => self::TYPE_SMALLINT,
44
        'bit' => self::TYPE_INTEGER,
45
        'smallint' => self::TYPE_SMALLINT,
46
        'mediumint' => self::TYPE_INTEGER,
47
        'int' => self::TYPE_INTEGER,
48
        'integer' => self::TYPE_INTEGER,
49
        'bigint' => self::TYPE_BIGINT,
50
        'float' => self::TYPE_FLOAT,
51
        'double' => self::TYPE_DOUBLE,
52
        'real' => self::TYPE_FLOAT,
53
        'decimal' => self::TYPE_DECIMAL,
54
        'numeric' => self::TYPE_DECIMAL,
55
        'tinytext' => self::TYPE_TEXT,
56
        'mediumtext' => self::TYPE_TEXT,
57
        'longtext' => self::TYPE_TEXT,
58
        'longblob' => self::TYPE_BINARY,
59
        'blob' => self::TYPE_BINARY,
60
        'text' => self::TYPE_TEXT,
61
        'varchar' => self::TYPE_STRING,
62
        'string' => self::TYPE_STRING,
63
        'char' => self::TYPE_CHAR,
64
        'datetime' => self::TYPE_DATETIME,
65
        'year' => self::TYPE_DATE,
66
        'date' => self::TYPE_DATE,
67
        'time' => self::TYPE_TIME,
68
        'timestamp' => self::TYPE_TIMESTAMP,
69
        'enum' => self::TYPE_STRING,
70
        'varbinary' => self::TYPE_BINARY,
71
        'json' => self::TYPE_JSON,
72
    ];
73
74
    /**
75
     * @inheritDoc
76
     */
77
    protected $tableQuoteCharacter = '`';
78
    /**
79
     * @inheritDoc
80
     */
81
    protected $columnQuoteCharacter = '`';
82
83
    /**
84
     * @inheritDoc
85
     */
86 62
    protected function resolveTableName($name)
87
    {
88 62
        $resolvedName = new TableSchema();
89 62
        $parts = explode('.', str_replace('`', '', $name));
90 62
        if (isset($parts[1])) {
91
            $resolvedName->schemaName = $parts[0];
92
            $resolvedName->name = $parts[1];
93
        } else {
94 62
            $resolvedName->schemaName = $this->defaultSchema;
95 62
            $resolvedName->name = $name;
96
        }
97 62
        $resolvedName->fullName = ($resolvedName->schemaName !== $this->defaultSchema ? $resolvedName->schemaName . '.' : '') . $resolvedName->name;
98 62
        return $resolvedName;
99
    }
100
101
    /**
102
     * @inheritDoc
103
     */
104 6
    protected function findTableNames($schema = '')
105
    {
106 6
        $sql = 'SHOW TABLES';
107 6
        if ($schema !== '') {
108
            $sql .= ' FROM ' . $this->quoteSimpleTableName($schema);
109
        }
110
111 6
        return $this->db->createCommand($sql)->queryColumn();
112
    }
113
114
    /**
115
     * @inheritDoc
116
     */
117 325
    protected function loadTableSchema($name)
118
    {
119 325
        $table = new TableSchema();
120 325
        $this->resolveTableNames($table, $name);
121
122 325
        if ($this->findColumns($table)) {
123 320
            $this->findConstraints($table);
124 320
            return $table;
125
        }
126
127 15
        return null;
128
    }
129
130
    /**
131
     * @inheritDoc
132
     */
133 35
    protected function loadTablePrimaryKey($tableName)
134
    {
135 35
        return $this->loadTableConstraints($tableName, 'primaryKey');
136
    }
137
138
    /**
139
     * @inheritDoc
140
     */
141 4
    protected function loadTableForeignKeys($tableName)
142
    {
143 4
        return $this->loadTableConstraints($tableName, 'foreignKeys');
144
    }
145
146
    /**
147
     * @inheritDoc
148
     */
149 32
    protected function loadTableIndexes($tableName)
150
    {
151 32
        static $sql = <<<'SQL'
152
SELECT
153
    `s`.`INDEX_NAME` AS `name`,
154
    `s`.`COLUMN_NAME` AS `column_name`,
155
    `s`.`NON_UNIQUE` ^ 1 AS `index_is_unique`,
156
    `s`.`INDEX_NAME` = 'PRIMARY' AS `index_is_primary`
157
FROM `information_schema`.`STATISTICS` AS `s`
158
WHERE `s`.`TABLE_SCHEMA` = COALESCE(:schemaName, DATABASE()) AND `s`.`INDEX_SCHEMA` = `s`.`TABLE_SCHEMA` AND `s`.`TABLE_NAME` = :tableName
159
ORDER BY `s`.`SEQ_IN_INDEX` ASC
160
SQL;
161
162 32
        $resolvedName = $this->resolveTableName($tableName);
163 32
        $indexes = $this->db->createCommand($sql, [
164 32
            ':schemaName' => $resolvedName->schemaName,
165 32
            ':tableName' => $resolvedName->name,
166 32
        ])->queryAll();
167 32
        $indexes = $this->normalizePdoRowKeyCase($indexes, true);
168 32
        $indexes = ArrayHelper::index($indexes, null, 'name');
169 32
        $result = [];
170 32
        foreach ($indexes as $name => $index) {
171 32
            $result[] = new IndexConstraint([
172 32
                'isPrimary' => (bool) $index[0]['index_is_primary'],
173 32
                'isUnique' => (bool) $index[0]['index_is_unique'],
174 32
                'name' => $name !== 'PRIMARY' ? $name : null,
175 32
                'columnNames' => ArrayHelper::getColumn($index, 'column_name'),
176
            ]);
177
        }
178
179 32
        return $result;
180
    }
181
182
    /**
183
     * @inheritDoc
184
     */
185 13
    protected function loadTableUniques($tableName)
186
    {
187 13
        return $this->loadTableConstraints($tableName, 'uniques');
188
    }
189
190
    /**
191
     * @inheritDoc
192
     * @throws NotSupportedException if this method is called.
193
     */
194 12
    protected function loadTableChecks($tableName)
0 ignored issues
show
Unused Code introduced by
The parameter $tableName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
195
    {
196 12
        throw new NotSupportedException('MySQL does not support check constraints.');
197
    }
198
199
    /**
200
     * @inheritDoc
201
     * @throws NotSupportedException if this method is called.
202
     */
203 12
    protected function loadTableDefaultValues($tableName)
0 ignored issues
show
Unused Code introduced by
The parameter $tableName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
204
    {
205 12
        throw new NotSupportedException('MySQL does not support default value constraints.');
206
    }
207
208
    /**
209
     * Creates a query builder for the MySQL database.
210
     * @return QueryBuilder query builder instance
211
     */
212 322
    public function createQueryBuilder()
213
    {
214 322
        return new QueryBuilder($this->db);
215
    }
216
217
    /**
218
     * Resolves the table name and schema name (if any).
219
     * @param TableSchema $table the table metadata object
220
     * @param string $name the table name
221
     */
222 325
    protected function resolveTableNames($table, $name)
223
    {
224 325
        $parts = explode('.', str_replace('`', '', $name));
225 325
        if (isset($parts[1])) {
226
            $table->schemaName = $parts[0];
227
            $table->name = $parts[1];
228
            $table->fullName = $table->schemaName . '.' . $table->name;
229
        } else {
230 325
            $table->fullName = $table->name = $parts[0];
231
        }
232 325
    }
233
234
    /**
235
     * Loads the column information into a [[ColumnSchema]] object.
236
     * @param array $info column information
237
     * @return ColumnSchema the column schema object
238
     */
239 320
    protected function loadColumnSchema($info)
240
    {
241 320
        $column = $this->createColumnSchema();
242
243 320
        $column->name = $info['field'];
244 320
        $column->allowNull = $info['null'] === 'YES';
245 320
        $column->isPrimaryKey = strpos($info['key'], 'PRI') !== false;
246 320
        $column->autoIncrement = stripos($info['extra'], 'auto_increment') !== false;
247 320
        $column->comment = $info['comment'];
248
249 320
        $column->dbType = $info['type'];
250 320
        $column->unsigned = stripos($column->dbType, 'unsigned') !== false;
251
252 320
        $column->type = self::TYPE_STRING;
253 320
        if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
254 320
            $type = strtolower($matches[1]);
255 320
            if (isset($this->typeMap[$type])) {
256 320
                $column->type = $this->typeMap[$type];
257
            }
258 320
            if (!empty($matches[2])) {
259 320
                if ($type === 'enum') {
260 24
                    preg_match_all("/'[^']*'/", $matches[2], $values);
261 24
                    foreach ($values[0] as $i => $value) {
262 24
                        $values[$i] = trim($value, "'");
263
                    }
264 24
                    $column->enumValues = $values;
0 ignored issues
show
Documentation Bug introduced by
It seems like $values can be null. However, the property $enumValues is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
265
                } else {
266 320
                    $values = explode(',', $matches[2]);
267 320
                    $column->size = $column->precision = (int) $values[0];
268 320
                    if (isset($values[1])) {
269 80
                        $column->scale = (int) $values[1];
270
                    }
271 320
                    if ($column->size === 1 && $type === 'bit') {
272 5
                        $column->type = 'boolean';
273 320
                    } elseif ($type === 'bit') {
274 24
                        if ($column->size > 32) {
275
                            $column->type = 'bigint';
276 24
                        } elseif ($column->size === 32) {
277
                            $column->type = 'integer';
278
                        }
279
                    }
280
                }
281
            }
282
        }
283
284 320
        $column->phpType = $this->getColumnPhpType($column);
285
286 320
        if (!$column->isPrimaryKey) {
287 316
            if (($column->type === 'timestamp' || $column->type ==='datetime') && $info['default'] === 'CURRENT_TIMESTAMP') {
288 26
                $column->defaultValue = new Expression('CURRENT_TIMESTAMP');
289 315
            } elseif (isset($type) && $type === 'bit') {
290 25
                $column->defaultValue = bindec(trim($info['default'], 'b\''));
291
            } else {
292 314
                $column->defaultValue = $column->phpTypecast($info['default']);
293
            }
294
        }
295
296 320
        return $column;
297
    }
298
299
    /**
300
     * Collects the metadata of table columns.
301
     * @param TableSchema $table the table metadata
302
     * @return bool whether the table exists in the database
303
     * @throws \Exception if DB query fails
304
     */
305 325
    protected function findColumns($table)
306
    {
307 325
        $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteTableName($table->fullName);
308
        try {
309 325
            $columns = $this->db->createCommand($sql)->queryAll();
310 15
        } catch (\Exception $e) {
311 15
            $previous = $e->getPrevious();
312 15
            if ($previous instanceof \PDOException && strpos($previous->getMessage(), 'SQLSTATE[42S02') !== false) {
313
                // table does not exist
314
                // https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html#error_er_bad_table_error
315 15
                return false;
316
            }
317
            throw $e;
318
        }
319 320
        foreach ($columns as $info) {
320 320
            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) !== \PDO::CASE_LOWER) {
321 319
                $info = array_change_key_case($info, CASE_LOWER);
322
            }
323 320
            $column = $this->loadColumnSchema($info);
324 320
            $table->columns[$column->name] = $column;
325 320
            if ($column->isPrimaryKey) {
326 295
                $table->primaryKey[] = $column->name;
327 295
                if ($column->autoIncrement) {
328 320
                    $table->sequenceName = '';
329
                }
330
            }
331
        }
332
333 320
        return true;
334
    }
335
336
    /**
337
     * Gets the CREATE TABLE sql string.
338
     * @param TableSchema $table the table metadata
339
     * @return string $sql the result of 'SHOW CREATE TABLE'
340
     */
341 1
    protected function getCreateTableSql($table)
342
    {
343 1
        $row = $this->db->createCommand('SHOW CREATE TABLE ' . $this->quoteTableName($table->fullName))->queryOne();
344 1
        if (isset($row['Create Table'])) {
345 1
            $sql = $row['Create Table'];
346
        } else {
347
            $row = array_values($row);
348
            $sql = $row[1];
349
        }
350
351 1
        return $sql;
352
    }
353
354
    /**
355
     * Collects the foreign key column details for the given table.
356
     * @param TableSchema $table the table metadata
357
     * @throws \Exception
358
     */
359 320
    protected function findConstraints($table)
360
    {
361
        $sql = <<<'SQL'
362 320
SELECT
363
    kcu.constraint_name,
364
    kcu.column_name,
365
    kcu.referenced_table_name,
366
    kcu.referenced_column_name
367
FROM information_schema.referential_constraints AS rc
368
JOIN information_schema.key_column_usage AS kcu ON
369
    (
370
        kcu.constraint_catalog = rc.constraint_catalog OR
371
        (kcu.constraint_catalog IS NULL AND rc.constraint_catalog IS NULL)
372
    ) AND
373
    kcu.constraint_schema = rc.constraint_schema AND
374
    kcu.constraint_name = rc.constraint_name
375
WHERE rc.constraint_schema = database() AND kcu.table_schema = database()
376
AND rc.table_name = :tableName AND kcu.table_name = :tableName1
377
SQL;
378
379
        try {
380 320
            $rows = $this->db->createCommand($sql, [':tableName' => $table->name, ':tableName1' => $table->name])->queryAll();
381 320
            $constraints = [];
382
383 320
            foreach ($rows as $row) {
384 213
                $constraints[$row['constraint_name']]['referenced_table_name'] = $row['referenced_table_name'];
385 213
                $constraints[$row['constraint_name']]['columns'][$row['column_name']] = $row['referenced_column_name'];
386
            }
387
388 320
            $table->foreignKeys = [];
389 320
            foreach ($constraints as $name => $constraint) {
390 213
                $table->foreignKeys[$name] = array_merge(
391 213
                    [$constraint['referenced_table_name']],
392 320
                    $constraint['columns']
393
                );
394
            }
395
        } catch (\Exception $e) {
396
            $previous = $e->getPrevious();
397
            if (!$previous instanceof \PDOException || strpos($previous->getMessage(), 'SQLSTATE[42S02') === false) {
398
                throw $e;
399
            }
400
401
            // table does not exist, try to determine the foreign keys using the table creation sql
402
            $sql = $this->getCreateTableSql($table);
403
            $regexp = '/FOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+([^\(^\s]+)\s*\(([^\)]+)\)/mi';
404
            if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) {
405
                foreach ($matches as $match) {
406
                    $fks = array_map('trim', explode(',', str_replace('`', '', $match[1])));
407
                    $pks = array_map('trim', explode(',', str_replace('`', '', $match[3])));
408
                    $constraint = [str_replace('`', '', $match[2])];
409
                    foreach ($fks as $k => $name) {
410
                        $constraint[$name] = $pks[$k];
411
                    }
412
                    $table->foreignKeys[md5(serialize($constraint))] = $constraint;
413
                }
414
                $table->foreignKeys = array_values($table->foreignKeys);
415
            }
416
        }
417 320
    }
418
419
    /**
420
     * Returns all unique indexes for the given table.
421
     *
422
     * Each array element is of the following structure:
423
     *
424
     * ```php
425
     * [
426
     *     'IndexName1' => ['col1' [, ...]],
427
     *     'IndexName2' => ['col2' [, ...]],
428
     * ]
429
     * ```
430
     *
431
     * @param TableSchema $table the table metadata
432
     * @return array all unique indexes for the given table.
433
     */
434 1
    public function findUniqueIndexes($table)
435
    {
436 1
        $sql = $this->getCreateTableSql($table);
437 1
        $uniqueIndexes = [];
438
439 1
        $regexp = '/UNIQUE KEY\s+\`(.+)\`\s*\((\`.+\`)+\)/mi';
440 1
        if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) {
441 1
            foreach ($matches as $match) {
442 1
                $indexName = $match[1];
443 1
                $indexColumns = array_map('trim', explode('`,`', trim($match[2], '`')));
444 1
                $uniqueIndexes[$indexName] = $indexColumns;
445
            }
446
        }
447
448 1
        return $uniqueIndexes;
449
    }
450
451
    /**
452
     * {@inheritdoc}
453
     */
454 16
    public function createColumnSchemaBuilder($type, $length = null)
455
    {
456 16
        return new ColumnSchemaBuilder($type, $length, $this->db);
457
    }
458
459
    /**
460
     * @return bool whether the version of the MySQL being used is older than 5.1.
461
     * @throws InvalidConfigException
462
     * @throws Exception
463
     * @since 2.0.13
464
     */
465
    protected function isOldMysql()
466
    {
467
        if ($this->_oldMysql === null) {
468
            $version = $this->db->getSlavePdo()->getAttribute(\PDO::ATTR_SERVER_VERSION);
469
            $this->_oldMysql = version_compare($version, '5.1', '<=');
470
        }
471
472
        return $this->_oldMysql;
473
    }
474
475
    /**
476
     * Loads multiple types of constraints and returns the specified ones.
477
     * @param string $tableName table name.
478
     * @param string $returnType return type:
479
     * - primaryKey
480
     * - foreignKeys
481
     * - uniques
482
     * @return mixed constraints.
483
     */
484 52
    private function loadTableConstraints($tableName, $returnType)
485
    {
486 52
        static $sql = <<<'SQL'
487
SELECT DISTINCT
488
    `kcu`.`CONSTRAINT_NAME` AS `name`,
489
    `kcu`.`COLUMN_NAME` AS `column_name`,
490
    `tc`.`CONSTRAINT_TYPE` AS `type`,
491
    CASE
492
        WHEN :schemaName IS NULL AND `kcu`.`REFERENCED_TABLE_SCHEMA` = `sch`.`name` THEN NULL
493
        ELSE `kcu`.`REFERENCED_TABLE_SCHEMA`
494
    END AS `foreign_table_schema`,
495
    `kcu`.`REFERENCED_TABLE_NAME` AS `foreign_table_name`,
496
    `kcu`.`REFERENCED_COLUMN_NAME` AS `foreign_column_name`,
497
    `rc`.`UPDATE_RULE` AS `on_update`,
498
    `rc`.`DELETE_RULE` AS `on_delete`,
499
    `kcu`.`ORDINAL_POSITION` as `position`
500
FROM (SELECT DATABASE() AS `name`) AS `sch`
501
INNER JOIN `information_schema`.`KEY_COLUMN_USAGE` AS `kcu`
502
    ON `kcu`.`TABLE_SCHEMA` = COALESCE(:schemaName, `sch`.`name`) AND `kcu`.`CONSTRAINT_SCHEMA` = `kcu`.`TABLE_SCHEMA` AND `kcu`.`TABLE_NAME` = :tableName
503
LEFT JOIN `information_schema`.`REFERENTIAL_CONSTRAINTS` AS `rc`
504
    ON `rc`.`CONSTRAINT_SCHEMA` = `kcu`.`TABLE_SCHEMA` AND `rc`.`CONSTRAINT_NAME` = `kcu`.`CONSTRAINT_NAME`
505
LEFT JOIN `information_schema`.`TABLE_CONSTRAINTS` AS `tc`
506
    ON `tc`.`TABLE_SCHEMA` = `kcu`.`TABLE_SCHEMA` AND `tc`.`CONSTRAINT_NAME` = `kcu`.`CONSTRAINT_NAME`
507
ORDER BY `kcu`.`ORDINAL_POSITION` ASC
508
SQL;
509
510 52
        $resolvedName = $this->resolveTableName($tableName);
511 52
        $constraints = $this->db->createCommand($sql, [
512 52
            ':schemaName' => $resolvedName->schemaName,
513 52
            ':tableName' => $resolvedName->name,
514 52
        ])->queryAll();
515 52
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
516 52
        $constraints = ArrayHelper::index($constraints, null, ['type', 'name']);
517
        $result = [
518 52
            'primaryKey' => null,
519
            'foreignKeys' => [],
520
            'uniques' => [],
521
        ];
522 52
        foreach ($constraints as $type => $names) {
523 52
            foreach ($names as $name => $constraint) {
524
                switch ($type) {
525 52
                    case 'PRIMARY KEY':
526 41
                        $result['primaryKey'] = new Constraint([
527 41
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
528
                        ]);
529 41
                        break;
530 46
                    case 'FOREIGN KEY':
531 10
                        $result['foreignKeys'][] = new ForeignKeyConstraint([
532 10
                            'name' => $name,
533 10
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
534 10
                            'foreignSchemaName' => $constraint[0]['foreign_table_schema'],
535 10
                            'foreignTableName' => $constraint[0]['foreign_table_name'],
536 10
                            'foreignColumnNames' => ArrayHelper::getColumn($constraint, 'foreign_column_name'),
537 10
                            'onDelete' => $constraint[0]['on_delete'],
538 10
                            'onUpdate' => $constraint[0]['on_update'],
539
                        ]);
540 10
                        break;
541 37
                    case 'UNIQUE':
542 37
                        $result['uniques'][] = new Constraint([
543 37
                            'name' => $name,
544 37
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
545
                        ]);
546 52
                        break;
547
                }
548
            }
549
        }
550 52
        foreach ($result as $type => $data) {
551 52
            $this->setTableMetadata($tableName, $type, $data);
552
        }
553
554 52
        return $result[$returnType];
555
    }
556
}
557