Passed
Push — master ( 9dbdd9...d5a428 )
by Alexander
04:15
created

framework/db/mysql/Schema.php (4 issues)

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 81
    protected function resolveTableName($name)
90
    {
91 81
        $resolvedName = new TableSchema();
92 81
        $parts = explode('.', str_replace('`', '', $name));
93 81
        if (isset($parts[1])) {
94
            $resolvedName->schemaName = $parts[0];
95
            $resolvedName->name = $parts[1];
96
        } else {
97 81
            $resolvedName->schemaName = $this->defaultSchema;
98 81
            $resolvedName->name = $name;
99
        }
100 81
        $resolvedName->fullName = ($resolvedName->schemaName !== $this->defaultSchema ? $resolvedName->schemaName . '.' : '') . $resolvedName->name;
101 81
        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 396
    protected function loadTableSchema($name)
121
    {
122 396
        $table = new TableSchema();
123 396
        $this->resolveTableNames($table, $name);
124
125 396
        if ($this->findColumns($table)) {
126 391
            $this->findConstraints($table);
127 391
            return $table;
128
        }
129
130 15
        return null;
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136 54
    protected function loadTablePrimaryKey($tableName)
137
    {
138 54
        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 51
    protected function loadTableIndexes($tableName)
153
    {
154 51
        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 51
        $resolvedName = $this->resolveTableName($tableName);
166 51
        $indexes = $this->db->createCommand($sql, [
167 51
            ':schemaName' => $resolvedName->schemaName,
168 51
            ':tableName' => $resolvedName->name,
169 51
        ])->queryAll();
170 51
        $indexes = $this->normalizePdoRowKeyCase($indexes, true);
171 51
        $indexes = ArrayHelper::index($indexes, null, 'name');
172 51
        $result = [];
173 51
        foreach ($indexes as $name => $index) {
174 51
            $result[] = new IndexConstraint([
175 51
                'isPrimary' => (bool) $index[0]['index_is_primary'],
176 51
                'isUnique' => (bool) $index[0]['index_is_unique'],
177 51
                'name' => $name !== 'PRIMARY' ? $name : null,
178 51
                'columnNames' => ArrayHelper::getColumn($index, 'column_name'),
179
            ]);
180
        }
181
182 51
        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
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
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 388
    public function createQueryBuilder()
216
    {
217 388
        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 396
    protected function resolveTableNames($table, $name)
226
    {
227 396
        $parts = explode('.', str_replace('`', '', $name));
228 396
        if (isset($parts[1])) {
229
            $table->schemaName = $parts[0];
230
            $table->name = $parts[1];
231
            $table->fullName = $table->schemaName . '.' . $table->name;
232
        } else {
233 396
            $table->fullName = $table->name = $parts[0];
234
        }
235 396
    }
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 392
    protected function loadColumnSchema($info)
243
    {
244 392
        $column = $this->createColumnSchema();
245
246 392
        $column->name = $info['field'];
247 392
        $column->allowNull = $info['null'] === 'YES';
248 392
        $column->isPrimaryKey = strpos($info['key'], 'PRI') !== false;
249 392
        $column->autoIncrement = stripos($info['extra'], 'auto_increment') !== false;
250 392
        $column->comment = $info['comment'];
251
252 392
        $column->dbType = $info['type'];
253 392
        $column->unsigned = stripos($column->dbType, 'unsigned') !== false;
254
255 392
        $column->type = self::TYPE_STRING;
256 392
        if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
257 392
            $type = strtolower($matches[1]);
258 392
            if (isset($this->typeMap[$type])) {
259 392
                $column->type = $this->typeMap[$type];
260
            }
261 392
            if (!empty($matches[2])) {
262 391
                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 391
                    $values = explode(',', $matches[2]);
270 391
                    $column->size = $column->precision = (int) $values[0];
271 391
                    if (isset($values[1])) {
272 101
                        $column->scale = (int) $values[1];
273
                    }
274 391
                    if ($column->size === 1 && $type === 'bit') {
275 6
                        $column->type = 'boolean';
276 391
                    } 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 392
        $column->phpType = $this->getColumnPhpType($column);
288
289 392
        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 387
            if (($column->type === 'timestamp' || $column->type === 'datetime')
297 387
                && preg_match('/^current_timestamp(?:\(([0-9]*)\))?$/i', $info['default'], $matches)) {
298 30
                $column->defaultValue = new Expression('CURRENT_TIMESTAMP' . (!empty($matches[1]) ? '(' . $matches[1] . ')' : ''));
299 384
            } elseif (isset($type) && $type === 'bit') {
300 28
                $column->defaultValue = bindec(trim($info['default'], 'b\''));
301
            } else {
302 383
                $column->defaultValue = $column->phpTypecast($info['default']);
303
            }
304
        }
305
306 392
        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 396
    protected function findColumns($table)
316
    {
317 396
        $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteTableName($table->fullName);
318
        try {
319 396
            $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 391
        foreach ($columns as $info) {
330 391
            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) !== \PDO::CASE_LOWER) {
331 390
                $info = array_change_key_case($info, CASE_LOWER);
332
            }
333 391
            $column = $this->loadColumnSchema($info);
334 391
            $table->columns[$column->name] = $column;
335 391
            if ($column->isPrimaryKey) {
336 364
                $table->primaryKey[] = $column->name;
337 364
                if ($column->autoIncrement) {
338 391
                    $table->sequenceName = '';
339
                }
340
            }
341
        }
342
343 391
        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);
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 391
    protected function findConstraints($table)
370
    {
371
        $sql = <<<'SQL'
372 391
SELECT
373
    `kcu`.`CONSTRAINT_NAME` AS `constraint_name`,
374
    `kcu`.`COLUMN_NAME` AS `column_name`,
375
    `kcu`.`REFERENCED_TABLE_NAME` AS `referenced_table_name`,
376
    `kcu`.`REFERENCED_COLUMN_NAME` AS `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 391
            $rows = $this->db->createCommand($sql, [':tableName' => $table->name, ':tableName1' => $table->name])->queryAll();
391 391
            $constraints = [];
392
393 391
            foreach ($rows as $row) {
394 266
                $constraints[$row['constraint_name']]['referenced_table_name'] = $row['referenced_table_name'];
395 266
                $constraints[$row['constraint_name']]['columns'][$row['column_name']] = $row['referenced_column_name'];
396
            }
397
398 391
            $table->foreignKeys = [];
399 391
            foreach ($constraints as $name => $constraint) {
400 266
                $table->foreignKeys[$name] = array_merge(
401 266
                    [$constraint['referenced_table_name']],
402 391
                    $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 391
    }
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', preg_split('/[`"],[`"]/', trim($match[2], '`"')));
454 1
                $uniqueIndexes[$indexName] = $indexColumns;
455
            }
456
        }
457
458 1
        return $uniqueIndexes;
459
    }
460
461
    /**
462
     * {@inheritdoc}
463
     */
464 17
    public function createColumnSchemaBuilder($type, $length = null)
465
    {
466 17
        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', '<=');
0 ignored issues
show
Documentation Bug introduced by
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 $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

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...
480
        }
481
482
        return $this->_oldMysql;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_oldMysql also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
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 71
    private function loadTableConstraints($tableName, $returnType)
495
    {
496 71
        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(:schemaName1, 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` = :tableName1 AND `rc`.`CONSTRAINT_NAME` = `kcu`.`CONSTRAINT_NAME`
517
    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'
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(:schemaName2, DATABASE()) AND `kcu`.`TABLE_NAME` = :tableName3
534
    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')
535
ORDER BY `position` ASC
536
SQL;
537
538 71
        $resolvedName = $this->resolveTableName($tableName);
539 71
        $constraints = $this->db->createCommand($sql, [
540 71
            ':schemaName' => $resolvedName->schemaName,
541 71
            ':schemaName1' => $resolvedName->schemaName,
542 71
            ':schemaName2' => $resolvedName->schemaName,
543 71
            ':tableName' => $resolvedName->name,
544 71
            ':tableName1' => $resolvedName->name,
545 71
            ':tableName2' => $resolvedName->name,
546 71
            ':tableName3' => $resolvedName->name,
547 71
            ':tableName4' => $resolvedName->name
548 71
        ])->queryAll();
549 71
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
550 71
        $constraints = ArrayHelper::index($constraints, null, ['type', 'name']);
551
        $result = [
552 71
            'primaryKey' => null,
553
            'foreignKeys' => [],
554
            'uniques' => [],
555
        ];
556 71
        foreach ($constraints as $type => $names) {
557 71
            foreach ($names as $name => $constraint) {
558
                switch ($type) {
559 71
                    case 'PRIMARY KEY':
560 60
                        $result['primaryKey'] = new Constraint([
561 60
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
562
                        ]);
563 60
                        break;
564 46
                    case 'FOREIGN KEY':
565 10
                        $result['foreignKeys'][] = new ForeignKeyConstraint([
566 10
                            'name' => $name,
567 10
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
568 10
                            'foreignSchemaName' => $constraint[0]['foreign_table_schema'],
569 10
                            'foreignTableName' => $constraint[0]['foreign_table_name'],
570 10
                            'foreignColumnNames' => ArrayHelper::getColumn($constraint, 'foreign_column_name'),
571 10
                            'onDelete' => $constraint[0]['on_delete'],
572 10
                            'onUpdate' => $constraint[0]['on_update'],
573
                        ]);
574 10
                        break;
575 37
                    case 'UNIQUE':
576 37
                        $result['uniques'][] = new Constraint([
577 37
                            'name' => $name,
578 37
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
579
                        ]);
580 71
                        break;
581
                }
582
            }
583
        }
584 71
        foreach ($result as $type => $data) {
585 71
            $this->setTableMetadata($tableName, $type, $data);
586
        }
587
588 71
        return $result[$returnType];
589
    }
590
}
591