Passed
Push — master ( 01b9fc...33798e )
by Wilmer
13:56 queued 12:28
created

Schema::loadColumnSchema()   D

Complexity

Conditions 19
Paths 100

Size

Total Lines 68
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 19.0389

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 19
eloc 44
c 1
b 0
f 0
nc 100
nop 1
dl 0
loc 68
ccs 40
cts 42
cp 0.9524
crap 19.0389
rs 4.5166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Mysql;
6
7
use Yiisoft\Arrays\ArrayHelper;
8
use Yiisoft\Db\Constraint\Constraint;
9
use Yiisoft\Db\Constraint\ConstraintFinderInterface;
10
use Yiisoft\Db\Constraint\ConstraintFinderTrait;
11
use Yiisoft\Db\Constraint\ForeignKeyConstraint;
12
use Yiisoft\Db\Constraint\IndexConstraint;
13
use Yiisoft\Db\Exception\InvalidConfigException;
14
use Yiisoft\Db\Exception\NotSupportedException;
15
use Yiisoft\Db\Expression\Expression;
16
use Yiisoft\Db\Schema\TableSchema;
17
18
/**
19
 * Schema is the class for retrieving metadata from a MySQL database (version 4.1.x and 5.x).
20
 */
21
class Schema extends \Yiisoft\Db\Schema\Schema implements ConstraintFinderInterface
22
{
23
    use ConstraintFinderTrait;
24
25
    /**
26
     * {@inheritdoc}
27
     */
28
    public string $columnSchemaClass = ColumnSchema::class;
29
30
    /**
31
     * @var bool whether MySQL used is older than 5.1.
32
     */
33
    private bool $oldMysql;
34
35
    /**
36
     * @var array mapping from physical column types (keys) to abstract column types (values)
37
     */
38
    public array $typeMap = [
39
        'tinyint' => self::TYPE_TINYINT,
40
        'bit' => self::TYPE_INTEGER,
41
        'smallint' => self::TYPE_SMALLINT,
42
        'mediumint' => self::TYPE_INTEGER,
43
        'int' => self::TYPE_INTEGER,
44
        'integer' => self::TYPE_INTEGER,
45
        'bigint' => self::TYPE_BIGINT,
46
        'float' => self::TYPE_FLOAT,
47
        'double' => self::TYPE_DOUBLE,
48
        'real' => self::TYPE_FLOAT,
49
        'decimal' => self::TYPE_DECIMAL,
50
        'numeric' => self::TYPE_DECIMAL,
51
        'tinytext' => self::TYPE_TEXT,
52
        'mediumtext' => self::TYPE_TEXT,
53
        'longtext' => self::TYPE_TEXT,
54
        'longblob' => self::TYPE_BINARY,
55
        'blob' => self::TYPE_BINARY,
56
        'text' => self::TYPE_TEXT,
57
        'varchar' => self::TYPE_STRING,
58
        'string' => self::TYPE_STRING,
59
        'char' => self::TYPE_CHAR,
60
        'datetime' => self::TYPE_DATETIME,
61
        'year' => self::TYPE_DATE,
62
        'date' => self::TYPE_DATE,
63
        'time' => self::TYPE_TIME,
64
        'timestamp' => self::TYPE_TIMESTAMP,
65
        'enum' => self::TYPE_STRING,
66
        'varbinary' => self::TYPE_BINARY,
67
        'json' => self::TYPE_JSON,
68
    ];
69
70
    /**
71
     * {@inheritdoc}
72
     */
73
    protected string $tableQuoteCharacter = '`';
74
75
    /**
76
     * {@inheritdoc}
77
     */
78
    protected string $columnQuoteCharacter = '`';
79
80
    /**
81
     * {@inheritdoc}
82
     */
83 58
    protected function resolveTableName($name)
84
    {
85 58
        $resolvedName = new TableSchema();
86
87 58
        $parts = explode('.', str_replace('`', '', $name));
88
89 58
        if (isset($parts[1])) {
90
            $resolvedName->schemaName = $parts[0];
91
            $resolvedName->name = $parts[1];
92
        } else {
93 58
            $resolvedName->schemaName = $this->defaultSchema;
94 58
            $resolvedName->name = $name;
95
        }
96
97 58
        $resolvedName->fullName = ($resolvedName->schemaName !== $this->defaultSchema ?
98 58
            $resolvedName->schemaName . '.' : '') . $resolvedName->name;
99
100 58
        return $resolvedName;
101
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106 5
    protected function findTableNames($schema = '')
107
    {
108 5
        $sql = 'SHOW TABLES';
109
110 5
        if ($schema !== '') {
111
            $sql .= ' FROM ' . $this->quoteSimpleTableName($schema);
112
        }
113
114 5
        return $this->db->createCommand($sql)->queryColumn();
0 ignored issues
show
Bug introduced by
The method createCommand() does not exist on null. ( Ignorable by Annotation )

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

114
        return $this->db->/** @scrutinizer ignore-call */ createCommand($sql)->queryColumn();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120 90
    protected function loadTableSchema(string $name): ?TableSchema
121
    {
122 90
        $table = new TableSchema();
123
124 90
        $this->resolveTableNames($table, $name);
125
126 90
        if ($this->findColumns($table)) {
127 84
            $this->findConstraints($table);
128
129 84
            return $table;
130
        }
131
132 14
        return null;
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138 31
    protected function loadTablePrimaryKey(string $tableName): ?Constraint
139
    {
140 31
        return $this->loadTableConstraints($tableName, 'primaryKey');
141
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146 4
    protected function loadTableForeignKeys(string $tableName): array
147
    {
148 4
        return $this->loadTableConstraints($tableName, 'foreignKeys');
149
    }
150
151
    /**
152
     * {@inheritdoc}
153
     */
154 28
    protected function loadTableIndexes(string $tableName): array
155
    {
156 28
        static $sql = <<<'SQL'
157
SELECT
158
    `s`.`INDEX_NAME` AS `name`,
159
    `s`.`COLUMN_NAME` AS `column_name`,
160
    `s`.`NON_UNIQUE` ^ 1 AS `index_is_unique`,
161
    `s`.`INDEX_NAME` = 'PRIMARY' AS `index_is_primary`
162
FROM `information_schema`.`STATISTICS` AS `s`
163
WHERE `s`.`TABLE_SCHEMA` = COALESCE(:schemaName, DATABASE()) AND `s`.`INDEX_SCHEMA` = `s`.`TABLE_SCHEMA` AND `s`.`TABLE_NAME` = :tableName
164
ORDER BY `s`.`SEQ_IN_INDEX` ASC
165
SQL;
166
167 28
        $resolvedName = $this->resolveTableName($tableName);
168
169 28
        $indexes = $this->db->createCommand($sql, [
170 28
            ':schemaName' => $resolvedName->schemaName,
171 28
            ':tableName' => $resolvedName->name,
172 28
        ])->queryAll();
173
174 28
        $indexes = $this->normalizePdoRowKeyCase($indexes, true);
175 28
        $indexes = ArrayHelper::index($indexes, null, 'name');
176 28
        $result = [];
177
178 28
        foreach ($indexes as $name => $index) {
179 28
            $ic = new IndexConstraint();
180
181 28
            $ic->setIsPrimary((bool) $index[0]['index_is_primary']);
182 28
            $ic->setIsUnique((bool) $index[0]['index_is_unique']);
183 28
            $ic->setName($name !== 'PRIMARY' ? $name : null);
184 28
            $ic->setColumnNames(ArrayHelper::getColumn($index, 'column_name'));
185
186 28
            $result[] = $ic;
187
        }
188
189 28
        return $result;
190
    }
191
192
    /**
193
     * {@inheritdoc}
194
     */
195 13
    protected function loadTableUniques(string $tableName): array
196
    {
197 13
        return $this->loadTableConstraints($tableName, 'uniques');
198
    }
199
200
    /**
201
     * {@inheritdoc}
202
     *
203
     * @throws NotSupportedException if this method is called.
204
     */
205 12
    protected function loadTableChecks(string $tableName): array
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

205
    protected function loadTableChecks(/** @scrutinizer ignore-unused */ string $tableName): array

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...
206
    {
207 12
        throw new NotSupportedException('MySQL does not support check constraints.');
208
    }
209
210
    /**
211
     * {@inheritdoc}
212
     *
213
     * @throws NotSupportedException if this method is called.
214
     */
215 12
    protected function loadTableDefaultValues(string $tableName): array
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

215
    protected function loadTableDefaultValues(/** @scrutinizer ignore-unused */ string $tableName): array

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...
216
    {
217 12
        throw new NotSupportedException('MySQL does not support default value constraints.');
218
    }
219
220
    /**
221
     * Creates a query builder for the MySQL database.
222
     *
223
     * @return QueryBuilder query builder instance
224
     */
225 62
    public function createQueryBuilder()
226
    {
227 62
        return new QueryBuilder($this->db);
0 ignored issues
show
Bug introduced by
It seems like $this->db can also be of type null; however, parameter $db of Yiisoft\Db\Mysql\QueryBuilder::__construct() does only seem to accept Yiisoft\Db\Connection\Connection, maybe add an additional type check? ( Ignorable by Annotation )

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

227
        return new QueryBuilder(/** @scrutinizer ignore-type */ $this->db);
Loading history...
228
    }
229
230
    /**
231
     * Resolves the table name and schema name (if any).
232
     *
233
     * @param TableSchema $table the table metadata object
234
     * @param string      $name  the table name
235
     */
236 90
    protected function resolveTableNames($table, $name)
237
    {
238 90
        $parts = explode('.', str_replace('`', '', $name));
239
240 90
        if (isset($parts[1])) {
241
            $table->schemaName = $parts[0];
242
            $table->name = $parts[1];
243
            $table->fullName = $table->schemaName . '.' . $table->name;
244
        } else {
245 90
            $table->fullName = $table->name = $parts[0];
246
        }
247 90
    }
248
249
    /**
250
     * Loads the column information into a {@see ColumnSchema} object.
251
     *
252
     * @param array $info column information
253
     *
254
     * @return ColumnSchema the column schema object
255
     */
256 85
    protected function loadColumnSchema(array $info): ColumnSchema
257
    {
258 85
        $column = $this->createColumnSchema();
259
260 85
        $column->name($info['field']);
261 85
        $column->allowNull($info['null'] === 'YES');
262 85
        $column->isPrimaryKey(strpos($info['key'], 'PRI') !== false);
263 85
        $column->autoIncrement(stripos($info['extra'], 'auto_increment') !== false);
264 85
        $column->comment($info['comment']);
265 85
        $column->dbType($info['type']);
266 85
        $column->unsigned(stripos($column->getDbType(), 'unsigned') !== false);
267 85
        $column->type(self::TYPE_STRING);
268
269 85
        if (preg_match('/^(\w+)(?:\(([^)]+)\))?/', $column->getDbType(), $matches)) {
270 85
            $type = strtolower($matches[1]);
271 85
            if (isset($this->typeMap[$type])) {
272 85
                $column->type($this->typeMap[$type]);
273
            }
274 85
            if (!empty($matches[2])) {
275 84
                if ($type === 'enum') {
276 22
                    preg_match_all("/'[^']*'/", $matches[2], $values);
277 22
                    foreach ($values[0] as $i => $value) {
278 22
                        $values[$i] = trim($value, "'");
279
                    }
280 22
                    $column->enumValues($values);
281
                } else {
282 84
                    $values = explode(',', $matches[2]);
283 84
                    $column->precision((int) $values[0]);
284 84
                    $column->size((int) $values[0]);
285 84
                    if (isset($values[1])) {
286 25
                        $column->scale((int) $values[1]);
287
                    }
288 84
                    if ($column->getSize() === 1 && $type === 'bit') {
289 3
                        $column->type('boolean');
290 84
                    } elseif ($type === 'bit') {
291 22
                        if ($column->getSize() > 32) {
292
                            $column->type('bigint');
293 22
                        } elseif ($column->getSize() === 32) {
294
                            $column->type('integer');
295
                        }
296
                    }
297
                }
298
            }
299
        }
300
301 85
        $column->phpType($this->getColumnPhpType($column));
302
303 85
        if (!$column->getIsPrimaryKey()) {
304
            /**
305
             * When displayed in the INFORMATION_SCHEMA.COLUMNS table, a default CURRENT TIMESTAMP is displayed
306
             * as CURRENT_TIMESTAMP up until MariaDB 10.2.2, and as current_timestamp() from MariaDB 10.2.3.
307
             *
308
             * See details here: https://mariadb.com/kb/en/library/now/#description
309
             */
310
            if (
311 83
                ($column->getType() === 'timestamp' || $column->getType() === 'datetime')
312 83
                && preg_match('/^current_timestamp(?:\(([0-9]*)\))?$/i', (string) $info['default'], $matches)
313
            ) {
314 25
                $column->defaultValue(new Expression('CURRENT_TIMESTAMP' . (!empty($matches[1])
315 25
                    ? '(' . $matches[1] . ')' : '')));
316 80
            } elseif (isset($type) && $type === 'bit') {
317 22
                $column->defaultValue(bindec(trim((string) $info['default'], 'b\'')));
318
            } else {
319 80
                $column->defaultValue($column->phpTypecast($info['default']));
320
            }
321
        }
322
323 85
        return $column;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $column returns the type Yiisoft\Db\Schema\ColumnSchema which includes types incompatible with the type-hinted return Yiisoft\Db\Mysql\ColumnSchema.
Loading history...
324
    }
325
326
    /**
327
     * Collects the metadata of table columns.
328
     *
329
     * @param TableSchema $table the table metadata
330
     *
331
     * @throws \Exception if DB query fails
332
     *
333
     * @return bool whether the table exists in the database
334
     */
335 90
    protected function findColumns($table)
336
    {
337 90
        $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteTableName($table->fullName);
338
339
        try {
340 90
            $columns = $this->db->createCommand($sql)->queryAll();
341 14
        } catch (\Exception $e) {
342 14
            $previous = $e->getPrevious();
343
344 14
            if ($previous instanceof \PDOException && strpos($previous->getMessage(), 'SQLSTATE[42S02') !== false) {
345
                // table does not exist
346
                // https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html#error_er_bad_table_error
347 14
                return false;
348
            }
349
350
            throw $e;
351
        }
352
353 84
        foreach ($columns as $info) {
354 84
            if ($this->db->getSlavePdo()->getAttribute(\PDO::ATTR_CASE) !== \PDO::CASE_LOWER) {
355 83
                $info = array_change_key_case($info, CASE_LOWER);
356
            }
357
358 84
            $column = $this->loadColumnSchema($info);
359 84
            $table->columns[$column->getName()] = $column;
360
361 84
            if ($column->getIsPrimaryKey()) {
362 60
                $table->primaryKey[] = $column->getName();
363 60
                if ($column->getAutoIncrement()) {
364 58
                    $table->sequenceName = '';
365
                }
366
            }
367
        }
368
369 84
        return true;
370
    }
371
372
    /**
373
     * Gets the CREATE TABLE sql string.
374
     *
375
     * @param TableSchema $table the table metadata
376
     *
377
     * @return string $sql the result of 'SHOW CREATE TABLE'
378
     */
379 1
    protected function getCreateTableSql($table): string
380
    {
381 1
        $row = $this->db->createCommand(
382 1
            'SHOW CREATE TABLE ' . $this->quoteTableName($table->fullName)
383 1
        )->queryOne();
384
385 1
        if (isset($row['Create Table'])) {
386 1
            $sql = $row['Create Table'];
387
        } else {
388
            $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

388
            $row = array_values(/** @scrutinizer ignore-type */ $row);
Loading history...
389
            $sql = $row[1];
390
        }
391
392 1
        return $sql;
393
    }
394
395
    /**
396
     * Collects the foreign key column details for the given table.
397
     *
398
     * @param TableSchema $table the table metadata
399
     *
400
     * @throws \Exception
401
     */
402 84
    protected function findConstraints($table)
403
    {
404
        $sql = <<<'SQL'
405 84
SELECT
406
    kcu.constraint_name,
407
    kcu.column_name,
408
    kcu.referenced_table_name,
409
    kcu.referenced_column_name
410
FROM information_schema.referential_constraints AS rc
411
JOIN information_schema.key_column_usage AS kcu ON
412
    (
413
        kcu.constraint_catalog = rc.constraint_catalog OR
414
        (kcu.constraint_catalog IS NULL AND rc.constraint_catalog IS NULL)
415
    ) AND
416
    kcu.constraint_schema = rc.constraint_schema AND
417
    kcu.constraint_name = rc.constraint_name
418
WHERE rc.constraint_schema = database() AND kcu.table_schema = database()
419
AND rc.table_name = :tableName AND kcu.table_name = :tableName1
420
SQL;
421
422
        try {
423 84
            $rows = $this->db->createCommand(
424 84
                $sql,
425 84
                [':tableName' => $table->name, ':tableName1' => $table->name]
426 84
            )->queryAll();
427
428 84
            $constraints = [];
429
430 84
            foreach ($rows as $row) {
431 27
                $constraints[$row['constraint_name']]['referenced_table_name'] = $row['referenced_table_name'];
432 27
                $constraints[$row['constraint_name']]['columns'][$row['column_name']] = $row['referenced_column_name'];
433
            }
434
435 84
            $table->foreignKeys = [];
436
437 84
            foreach ($constraints as $name => $constraint) {
438 27
                $table->foreignKeys[$name] = array_merge(
439 27
                    [$constraint['referenced_table_name']],
440 27
                    $constraint['columns']
441
                );
442
            }
443
        } catch (\Exception $e) {
444
            $previous = $e->getPrevious();
445
446
            if (!$previous instanceof \PDOException || strpos($previous->getMessage(), 'SQLSTATE[42S02') === false) {
447
                throw $e;
448
            }
449
450
            // table does not exist, try to determine the foreign keys using the table creation sql
451
            $sql = $this->getCreateTableSql($table);
452
            $regexp = '/FOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+([^\(^\s]+)\s*\(([^\)]+)\)/mi';
453
454
            if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) {
455
                foreach ($matches as $match) {
456
                    $fks = array_map('trim', explode(',', str_replace('`', '', $match[1])));
457
                    $pks = array_map('trim', explode(',', str_replace('`', '', $match[3])));
458
                    $constraint = [str_replace('`', '', $match[2])];
459
460
                    foreach ($fks as $k => $name) {
461
                        $constraint[$name] = $pks[$k];
462
                    }
463
464
                    $table->foreignKeys[md5(serialize($constraint))] = $constraint;
465
                }
466
                $table->foreignKeys = array_values($table->foreignKeys);
467
            }
468
        }
469 84
    }
470
471
    /**
472
     * Returns all unique indexes for the given table.
473
     *
474
     * Each array element is of the following structure:
475
     *
476
     * ```php
477
     * [
478
     *     'IndexName1' => ['col1' [, ...]],
479
     *     'IndexName2' => ['col2' [, ...]],
480
     * ]
481
     * ```
482
     *
483
     * @param TableSchema $table the table metadata
484
     *
485
     * @return array all unique indexes for the given table.
486
     */
487 1
    public function findUniqueIndexes($table)
488
    {
489 1
        $sql = $this->getCreateTableSql($table);
490
491 1
        $uniqueIndexes = [];
492
493 1
        $regexp = '/UNIQUE KEY\s+\`(.+)\`\s*\((\`.+\`)+\)/mi';
494
495 1
        if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) {
496 1
            foreach ($matches as $match) {
497 1
                $indexName = $match[1];
498 1
                $indexColumns = array_map('trim', explode('`,`', trim($match[2], '`')));
499 1
                $uniqueIndexes[$indexName] = $indexColumns;
500
            }
501
        }
502
503 1
        return $uniqueIndexes;
504
    }
505
506
    /**
507
     * {@inheritdoc}
508
     */
509 1
    public function createColumnSchemaBuilder($type, $length = null)
510
    {
511 1
        return new ColumnSchemaBuilder($type, $length, $this->db);
512
    }
513
514
    /**
515
     * @throws InvalidConfigException
516
     * @throws Exception
517
     *
518
     * @return bool whether the version of the MySQL being used is older than 5.1.
519
     */
520
    protected function isOldMysql()
521
    {
522
        if ($this->oldMysql === null) {
523
            $version = $this->db->getSlavePdo()->getAttribute(\PDO::ATTR_SERVER_VERSION);
524
525
            $this->oldMysql = version_compare($version, '5.1', '<=');
526
        }
527
528
        return $this->oldMysql;
529
    }
530
531
    /**
532
     * Loads multiple types of constraints and returns the specified ones.
533
     *
534
     * @param string $tableName table name.
535
     * @param string $returnType return type:
536
     * - primaryKey
537
     * - foreignKeys
538
     * - uniques
539
     *
540
     * @return mixed constraints.
541
     */
542 48
    private function loadTableConstraints(string $tableName, string $returnType)
543
    {
544 48
        static $sql = <<<'SQL'
545
SELECT
546
    `kcu`.`CONSTRAINT_NAME` AS `name`,
547
    `kcu`.`COLUMN_NAME` AS `column_name`,
548
    `tc`.`CONSTRAINT_TYPE` AS `type`,
549
    CASE
550
        WHEN :schemaName IS NULL AND `kcu`.`REFERENCED_TABLE_SCHEMA` = DATABASE() THEN NULL
551
        ELSE `kcu`.`REFERENCED_TABLE_SCHEMA`
552
    END AS `foreign_table_schema`,
553
    `kcu`.`REFERENCED_TABLE_NAME` AS `foreign_table_name`,
554
    `kcu`.`REFERENCED_COLUMN_NAME` AS `foreign_column_name`,
555
    `rc`.`UPDATE_RULE` AS `on_update`,
556
    `rc`.`DELETE_RULE` AS `on_delete`,
557
    `kcu`.`ORDINAL_POSITION` AS `position`
558
FROM
559
    `information_schema`.`KEY_COLUMN_USAGE` AS `kcu`,
560
    `information_schema`.`REFERENTIAL_CONSTRAINTS` AS `rc`,
561
    `information_schema`.`TABLE_CONSTRAINTS` AS `tc`
562
WHERE
563
    `kcu`.`TABLE_SCHEMA` = COALESCE(:schemaName, DATABASE()) AND `kcu`.`CONSTRAINT_SCHEMA` = `kcu`.`TABLE_SCHEMA` AND `kcu`.`TABLE_NAME` = :tableName
564
    AND `rc`.`CONSTRAINT_SCHEMA` = `kcu`.`TABLE_SCHEMA` AND `rc`.`TABLE_NAME` = :tableName AND `rc`.`CONSTRAINT_NAME` = `kcu`.`CONSTRAINT_NAME`
565
    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'
566
UNION
567
SELECT
568
    `kcu`.`CONSTRAINT_NAME` AS `name`,
569
    `kcu`.`COLUMN_NAME` AS `column_name`,
570
    `tc`.`CONSTRAINT_TYPE` AS `type`,
571
    NULL AS `foreign_table_schema`,
572
    NULL AS `foreign_table_name`,
573
    NULL AS `foreign_column_name`,
574
    NULL AS `on_update`,
575
    NULL AS `on_delete`,
576
    `kcu`.`ORDINAL_POSITION` AS `position`
577
FROM
578
    `information_schema`.`KEY_COLUMN_USAGE` AS `kcu`,
579
    `information_schema`.`TABLE_CONSTRAINTS` AS `tc`
580
WHERE
581
    `kcu`.`TABLE_SCHEMA` = COALESCE(:schemaName, DATABASE()) AND `kcu`.`TABLE_NAME` = :tableName
582
    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')
583
ORDER BY `position` ASC
584
SQL;
585
586 48
        $resolvedName = $this->resolveTableName($tableName);
587
588 48
        $constraints = $this->db->createCommand(
589 48
            $sql,
590
            [
591 48
                ':schemaName' => $resolvedName->schemaName,
592 48
                ':tableName' => $resolvedName->name,
593
            ]
594 48
        )->queryAll();
595
596 48
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
597 48
        $constraints = ArrayHelper::index($constraints, null, ['type', 'name']);
598
599
        $result = [
600 48
            'primaryKey' => null,
601
            'foreignKeys' => [],
602
            'uniques' => [],
603
        ];
604
605 48
        foreach ($constraints as $type => $names) {
606 48
            foreach ($names as $name => $constraint) {
607 48
                switch ($type) {
608 48
                    case 'PRIMARY KEY':
609 37
                        $ct = new Constraint();
610 37
                        $ct->setColumnNames(ArrayHelper::getColumn($constraint, 'column_name'));
611 37
                        $result['primaryKey'] = $ct;
612
613 37
                        break;
614 46
                    case 'FOREIGN KEY':
615 10
                        $fk = new ForeignKeyConstraint();
616 10
                        $fk->setName($name);
617 10
                        $fk->setColumnNames(ArrayHelper::getColumn($constraint, 'column_name'));
618 10
                        $fk->setForeignSchemaName($constraint[0]['foreign_table_schema']);
619 10
                        $fk->setForeignTableName($constraint[0]['foreign_table_name']);
620 10
                        $fk->setForeignColumnNames(ArrayHelper::getColumn($constraint, 'foreign_column_name'));
621 10
                        $fk->setOnDelete($constraint[0]['on_delete']);
622 10
                        $fk->setOnUpdate($constraint[0]['on_update']);
623 10
                        $result['foreignKeys'][] = $fk;
624
625 10
                        break;
626 37
                    case 'UNIQUE':
627 37
                        $ct = new Constraint();
628 37
                        $ct->setName($name);
629 37
                        $ct->setColumnNames(ArrayHelper::getColumn($constraint, 'column_name'));
630 37
                        $result['uniques'][] = $ct;
631
632 37
                        break;
633
                }
634
            }
635
        }
636
637 48
        foreach ($result as $type => $data) {
638 48
            $this->setTableMetadata($tableName, $type, $data);
639
        }
640
641 48
        return $result[$returnType];
642
    }
643
}
644