Passed
Push — fix-tests ( 6dd538...727178 )
by Alexander
195:41 queued 192:18
created

Schema   F

Complexity

Total Complexity 69

Size/Duplication

Total Lines 555
Duplicated Lines 0 %

Test Coverage

Coverage 84.73%

Importance

Changes 0
Metric Value
eloc 294
dl 0
loc 555
ccs 172
cts 203
cp 0.8473
rs 2.88
c 0
b 0
f 0
wmc 69

19 Methods

Rating   Name   Duplication   Size   Complexity  
A loadTableChecks() 0 3 1
B findColumns() 0 29 8
A loadTableUniques() 0 3 1
A createColumnSchemaBuilder() 0 3 1
A loadTableIndexes() 0 31 3
A isOldMysql() 0 8 2
A loadTablePrimaryKey() 0 3 1
A resolveTableNames() 0 9 2
B loadTableConstraints() 0 89 7
A resolveTableName() 0 13 3
A createQueryBuilder() 0 3 1
A loadTableForeignKeys() 0 3 1
D loadColumnSchema() 0 65 19
A loadTableDefaultValues() 0 3 1
A loadTableSchema() 0 11 2
A findTableNames() 0 8 2
B findConstraints() 0 56 9
A findUniqueIndexes() 0 15 3
A getCreateTableSql() 0 11 2

How to fix   Complexity   

Complex Class

Complex classes like Schema often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Schema, and based on these observations, apply Extract Interface, too.

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

197
    protected function loadTableChecks(/** @scrutinizer ignore-unused */ $tableName)

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

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

206
    protected function loadTableDefaultValues(/** @scrutinizer ignore-unused */ $tableName)

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

Loading history...
207
    {
208 12
        throw new NotSupportedException('MySQL does not support default value constraints.');
209
    }
210
211
    /**
212
     * Creates a query builder for the MySQL database.
213
     * @return QueryBuilder query builder instance
214
     */
215 378
    public function createQueryBuilder()
216
    {
217 378
        return new QueryBuilder($this->db);
218
    }
219
220
    /**
221
     * Resolves the table name and schema name (if any).
222
     * @param TableSchema $table the table metadata object
223
     * @param string $name the table name
224
     */
225 390
    protected function resolveTableNames($table, $name)
226
    {
227 390
        $parts = explode('.', str_replace('`', '', $name));
228 390
        if (isset($parts[1])) {
229
            $table->schemaName = $parts[0];
230
            $table->name = $parts[1];
231
            $table->fullName = $table->schemaName . '.' . $table->name;
232
        } else {
233 390
            $table->fullName = $table->name = $parts[0];
234
        }
235 390
    }
236
237
    /**
238
     * Loads the column information into a [[ColumnSchema]] object.
239
     * @param array $info column information
240
     * @return ColumnSchema the column schema object
241
     */
242 386
    protected function loadColumnSchema($info)
243
    {
244 386
        $column = $this->createColumnSchema();
245
246 386
        $column->name = $info['field'];
247 386
        $column->allowNull = $info['null'] === 'YES';
248 386
        $column->isPrimaryKey = strpos($info['key'], 'PRI') !== false;
249 386
        $column->autoIncrement = stripos($info['extra'], 'auto_increment') !== false;
250 386
        $column->comment = $info['comment'];
251
252 386
        $column->dbType = $info['type'];
253 386
        $column->unsigned = stripos($column->dbType, 'unsigned') !== false;
254
255 386
        $column->type = self::TYPE_STRING;
256 386
        if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
257 386
            $type = strtolower($matches[1]);
258 386
            if (isset($this->typeMap[$type])) {
259 386
                $column->type = $this->typeMap[$type];
260
            }
261 386
            if (!empty($matches[2])) {
262 385
                if ($type === 'enum') {
263 27
                    preg_match_all("/'[^']*'/", $matches[2], $values);
264 27
                    foreach ($values[0] as $i => $value) {
265 27
                        $values[$i] = trim($value, "'");
266
                    }
267 27
                    $column->enumValues = $values;
268
                } else {
269 385
                    $values = explode(',', $matches[2]);
270 385
                    $column->size = $column->precision = (int) $values[0];
271 385
                    if (isset($values[1])) {
272 98
                        $column->scale = (int) $values[1];
273
                    }
274 385
                    if ($column->size === 1 && $type === 'bit') {
275 6
                        $column->type = 'boolean';
276 385
                    } elseif ($type === 'bit') {
277 27
                        if ($column->size > 32) {
278
                            $column->type = 'bigint';
279 27
                        } elseif ($column->size === 32) {
280
                            $column->type = 'integer';
281
                        }
282
                    }
283
                }
284
            }
285
        }
286
287 386
        $column->phpType = $this->getColumnPhpType($column);
288
289 386
        if (!$column->isPrimaryKey) {
290
            /**
291
             * When displayed in the INFORMATION_SCHEMA.COLUMNS table, a default CURRENT TIMESTAMP is displayed
292
             * as CURRENT_TIMESTAMP up until MariaDB 10.2.2, and as current_timestamp() from MariaDB 10.2.3.
293
             *
294
             * See details here: https://mariadb.com/kb/en/library/now/#description
295
             */
296 381
            if (($column->type === 'timestamp' || $column->type === 'datetime')
297 381
                && preg_match('/^current_timestamp(?:\(([0-9]*)\))?$/i', $info['default'], $matches)) {
298 30
                $column->defaultValue = new Expression('CURRENT_TIMESTAMP' . (!empty($matches[1]) ? '(' . $matches[1] . ')' : ''));
299 378
            } elseif (isset($type) && $type === 'bit') {
300 28
                $column->defaultValue = bindec(trim($info['default'], 'b\''));
301
            } else {
302 377
                $column->defaultValue = $column->phpTypecast($info['default']);
303
            }
304
        }
305
306 386
        return $column;
307
    }
308
309
    /**
310
     * Collects the metadata of table columns.
311
     * @param TableSchema $table the table metadata
312
     * @return bool whether the table exists in the database
313
     * @throws \Exception if DB query fails
314
     */
315 390
    protected function findColumns($table)
316
    {
317 390
        $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteTableName($table->fullName);
318
        try {
319 390
            $columns = $this->db->createCommand($sql)->queryAll();
320 15
        } catch (\Exception $e) {
321 15
            $previous = $e->getPrevious();
322 15
            if ($previous instanceof \PDOException && strpos($previous->getMessage(), 'SQLSTATE[42S02') !== false) {
323
                // table does not exist
324
                // https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html#error_er_bad_table_error
325 15
                return false;
326
            }
327
            throw $e;
328
        }
329 385
        foreach ($columns as $info) {
330 385
            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) !== \PDO::CASE_LOWER) {
331 384
                $info = array_change_key_case($info, CASE_LOWER);
332
            }
333 385
            $column = $this->loadColumnSchema($info);
334 385
            $table->columns[$column->name] = $column;
335 385
            if ($column->isPrimaryKey) {
336 358
                $table->primaryKey[] = $column->name;
337 358
                if ($column->autoIncrement) {
338 385
                    $table->sequenceName = '';
339
                }
340
            }
341
        }
342
343 385
        return true;
344
    }
345
346
    /**
347
     * Gets the CREATE TABLE sql string.
348
     * @param TableSchema $table the table metadata
349
     * @return string $sql the result of 'SHOW CREATE TABLE'
350
     */
351 1
    protected function getCreateTableSql($table)
352
    {
353 1
        $row = $this->db->createCommand('SHOW CREATE TABLE ' . $this->quoteTableName($table->fullName))->queryOne();
354 1
        if (isset($row['Create Table'])) {
355 1
            $sql = $row['Create Table'];
356
        } else {
357
            $row = array_values($row);
0 ignored issues
show
Bug introduced by
$row of type false is incompatible with the type array expected by parameter $input of array_values(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

357
            $row = array_values(/** @scrutinizer ignore-type */ $row);
Loading history...
358
            $sql = $row[1];
359
        }
360
361 1
        return $sql;
362
    }
363
364
    /**
365
     * Collects the foreign key column details for the given table.
366
     * @param TableSchema $table the table metadata
367
     * @throws \Exception
368
     */
369 385
    protected function findConstraints($table)
370
    {
371
        $sql = <<<'SQL'
372 385
SELECT
373
    kcu.constraint_name,
374
    kcu.column_name,
375
    kcu.referenced_table_name,
376
    kcu.referenced_column_name
377
FROM information_schema.referential_constraints AS rc
378
JOIN information_schema.key_column_usage AS kcu ON
379
    (
380
        kcu.constraint_catalog = rc.constraint_catalog OR
381
        (kcu.constraint_catalog IS NULL AND rc.constraint_catalog IS NULL)
382
    ) AND
383
    kcu.constraint_schema = rc.constraint_schema AND
384
    kcu.constraint_name = rc.constraint_name
385
WHERE rc.constraint_schema = database() AND kcu.table_schema = database()
386
AND rc.table_name = :tableName AND kcu.table_name = :tableName1
387
SQL;
388
389
        try {
390 385
            $rows = $this->db->createCommand($sql, [':tableName' => $table->name, ':tableName1' => $table->name])->queryAll();
391 385
            $constraints = [];
392
393 385
            foreach ($rows as $row) {
394 262
                $constraints[$row['constraint_name']]['referenced_table_name'] = $row['referenced_table_name'];
395 262
                $constraints[$row['constraint_name']]['columns'][$row['column_name']] = $row['referenced_column_name'];
396
            }
397
398 385
            $table->foreignKeys = [];
399 385
            foreach ($constraints as $name => $constraint) {
400 262
                $table->foreignKeys[$name] = array_merge(
401 262
                    [$constraint['referenced_table_name']],
402 385
                    $constraint['columns']
403
                );
404
            }
405
        } catch (\Exception $e) {
406
            $previous = $e->getPrevious();
407
            if (!$previous instanceof \PDOException || strpos($previous->getMessage(), 'SQLSTATE[42S02') === false) {
408
                throw $e;
409
            }
410
411
            // table does not exist, try to determine the foreign keys using the table creation sql
412
            $sql = $this->getCreateTableSql($table);
413
            $regexp = '/FOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+([^\(^\s]+)\s*\(([^\)]+)\)/mi';
414
            if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) {
415
                foreach ($matches as $match) {
416
                    $fks = array_map('trim', explode(',', str_replace('`', '', $match[1])));
417
                    $pks = array_map('trim', explode(',', str_replace('`', '', $match[3])));
418
                    $constraint = [str_replace('`', '', $match[2])];
419
                    foreach ($fks as $k => $name) {
420
                        $constraint[$name] = $pks[$k];
421
                    }
422
                    $table->foreignKeys[md5(serialize($constraint))] = $constraint;
423
                }
424
                $table->foreignKeys = array_values($table->foreignKeys);
425
            }
426
        }
427 385
    }
428
429
    /**
430
     * Returns all unique indexes for the given table.
431
     *
432
     * Each array element is of the following structure:
433
     *
434
     * ```php
435
     * [
436
     *     'IndexName1' => ['col1' [, ...]],
437
     *     'IndexName2' => ['col2' [, ...]],
438
     * ]
439
     * ```
440
     *
441
     * @param TableSchema $table the table metadata
442
     * @return array all unique indexes for the given table.
443
     */
444 1
    public function findUniqueIndexes($table)
445
    {
446 1
        $sql = $this->getCreateTableSql($table);
447 1
        $uniqueIndexes = [];
448
449 1
        $regexp = '/UNIQUE KEY\s+\`(.+)\`\s*\((\`.+\`)+\)/mi';
450 1
        if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) {
451 1
            foreach ($matches as $match) {
452 1
                $indexName = $match[1];
453 1
                $indexColumns = array_map('trim', explode('`,`', trim($match[2], '`')));
454 1
                $uniqueIndexes[$indexName] = $indexColumns;
455
            }
456
        }
457
458 1
        return $uniqueIndexes;
459
    }
460
461
    /**
462
     * {@inheritdoc}
463
     */
464 15
    public function createColumnSchemaBuilder($type, $length = null)
465
    {
466 15
        return new ColumnSchemaBuilder($type, $length, $this->db);
467
    }
468
469
    /**
470
     * @return bool whether the version of the MySQL being used is older than 5.1.
471
     * @throws InvalidConfigException
472
     * @throws Exception
473
     * @since 2.0.13
474
     */
475
    protected function isOldMysql()
476
    {
477
        if ($this->_oldMysql === null) {
478
            $version = $this->db->getSlavePdo()->getAttribute(\PDO::ATTR_SERVER_VERSION);
479
            $this->_oldMysql = version_compare($version, '5.1', '<=');
480
        }
481
482
        return $this->_oldMysql;
483
    }
484
485
    /**
486
     * Loads multiple types of constraints and returns the specified ones.
487
     * @param string $tableName table name.
488
     * @param string $returnType return type:
489
     * - primaryKey
490
     * - foreignKeys
491
     * - uniques
492
     * @return mixed constraints.
493
     */
494 70
    private function loadTableConstraints($tableName, $returnType)
495
    {
496 70
        static $sql = <<<'SQL'
497
SELECT
498
    `kcu`.`CONSTRAINT_NAME` AS `name`,
499
    `kcu`.`COLUMN_NAME` AS `column_name`,
500
    `tc`.`CONSTRAINT_TYPE` AS `type`,
501
    CASE
502
        WHEN :schemaName IS NULL AND `kcu`.`REFERENCED_TABLE_SCHEMA` = DATABASE() THEN NULL
503
        ELSE `kcu`.`REFERENCED_TABLE_SCHEMA`
504
    END AS `foreign_table_schema`,
505
    `kcu`.`REFERENCED_TABLE_NAME` AS `foreign_table_name`,
506
    `kcu`.`REFERENCED_COLUMN_NAME` AS `foreign_column_name`,
507
    `rc`.`UPDATE_RULE` AS `on_update`,
508
    `rc`.`DELETE_RULE` AS `on_delete`,
509
    `kcu`.`ORDINAL_POSITION` AS `position`
510
FROM
511
    `information_schema`.`KEY_COLUMN_USAGE` AS `kcu`,
512
    `information_schema`.`REFERENTIAL_CONSTRAINTS` AS `rc`,
513
    `information_schema`.`TABLE_CONSTRAINTS` AS `tc`
514
WHERE
515
    `kcu`.`TABLE_SCHEMA` = COALESCE(:schemaName, DATABASE()) AND `kcu`.`CONSTRAINT_SCHEMA` = `kcu`.`TABLE_SCHEMA` AND `kcu`.`TABLE_NAME` = :tableName
516
    AND `rc`.`CONSTRAINT_SCHEMA` = `kcu`.`TABLE_SCHEMA` AND `rc`.`TABLE_NAME` = :tableName AND `rc`.`CONSTRAINT_NAME` = `kcu`.`CONSTRAINT_NAME`
517
    AND `tc`.`TABLE_SCHEMA` = `kcu`.`TABLE_SCHEMA` AND `tc`.`TABLE_NAME` = :tableName AND `tc`.`CONSTRAINT_NAME` = `kcu`.`CONSTRAINT_NAME` AND `tc`.`CONSTRAINT_TYPE` = 'FOREIGN KEY'
518
UNION
519
SELECT
520
    `kcu`.`CONSTRAINT_NAME` AS `name`,
521
    `kcu`.`COLUMN_NAME` AS `column_name`,
522
    `tc`.`CONSTRAINT_TYPE` AS `type`,
523
    NULL AS `foreign_table_schema`,
524
    NULL AS `foreign_table_name`,
525
    NULL AS `foreign_column_name`,
526
    NULL AS `on_update`,
527
    NULL AS `on_delete`,
528
    `kcu`.`ORDINAL_POSITION` AS `position`
529
FROM
530
    `information_schema`.`KEY_COLUMN_USAGE` AS `kcu`,
531
    `information_schema`.`TABLE_CONSTRAINTS` AS `tc`
532
WHERE
533
    `kcu`.`TABLE_SCHEMA` = COALESCE(:schemaName, DATABASE()) AND `kcu`.`TABLE_NAME` = :tableName
534
    AND `tc`.`TABLE_SCHEMA` = `kcu`.`TABLE_SCHEMA` AND `tc`.`TABLE_NAME` = :tableName AND `tc`.`CONSTRAINT_NAME` = `kcu`.`CONSTRAINT_NAME` AND `tc`.`CONSTRAINT_TYPE` IN ('PRIMARY KEY', 'UNIQUE')
535
ORDER BY `position` ASC
536
SQL;
537
538 70
        $resolvedName = $this->resolveTableName($tableName);
539 70
        $constraints = $this->db->createCommand($sql, [
540 70
            ':schemaName' => $resolvedName->schemaName,
541 70
            ':tableName' => $resolvedName->name,
542 70
        ])->queryAll();
543 70
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
544 70
        $constraints = ArrayHelper::index($constraints, null, ['type', 'name']);
545
        $result = [
546 70
            'primaryKey' => null,
547
            'foreignKeys' => [],
548
            'uniques' => [],
549
        ];
550 70
        foreach ($constraints as $type => $names) {
551 70
            foreach ($names as $name => $constraint) {
552
                switch ($type) {
553 70
                    case 'PRIMARY KEY':
554 59
                        $result['primaryKey'] = new Constraint([
555 59
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
556
                        ]);
557 59
                        break;
558 46
                    case 'FOREIGN KEY':
559 10
                        $result['foreignKeys'][] = new ForeignKeyConstraint([
560 10
                            'name' => $name,
561 10
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
562 10
                            'foreignSchemaName' => $constraint[0]['foreign_table_schema'],
563 10
                            'foreignTableName' => $constraint[0]['foreign_table_name'],
564 10
                            'foreignColumnNames' => ArrayHelper::getColumn($constraint, 'foreign_column_name'),
565 10
                            'onDelete' => $constraint[0]['on_delete'],
566 10
                            'onUpdate' => $constraint[0]['on_update'],
567
                        ]);
568 10
                        break;
569 37
                    case 'UNIQUE':
570 37
                        $result['uniques'][] = new Constraint([
571 37
                            'name' => $name,
572 37
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
573
                        ]);
574 70
                        break;
575
                }
576
            }
577
        }
578 70
        foreach ($result as $type => $data) {
579 70
            $this->setTableMetadata($tableName, $type, $data);
580
        }
581
582 70
        return $result[$returnType];
583
    }
584
}
585