Passed
Push — master ( 4d46cd...032e1b )
by Def
35:26 queued 21:19
created

Schema::getPragmaIndexInfo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Sqlite;
6
7
use Throwable;
8
use Yiisoft\Db\Constraint\CheckConstraint;
9
use Yiisoft\Db\Constraint\Constraint;
10
use Yiisoft\Db\Constraint\ForeignKeyConstraint;
11
use Yiisoft\Db\Constraint\IndexConstraint;
12
use Yiisoft\Db\Driver\Pdo\AbstractPdoSchema;
13
use Yiisoft\Db\Exception\Exception;
14
use Yiisoft\Db\Exception\InvalidArgumentException;
15
use Yiisoft\Db\Exception\InvalidConfigException;
16
use Yiisoft\Db\Exception\NotSupportedException;
17
use Yiisoft\Db\Expression\Expression;
18
use Yiisoft\Db\Helper\DbArrayHelper;
19
use Yiisoft\Db\Schema\Builder\ColumnInterface;
20
use Yiisoft\Db\Schema\ColumnSchemaInterface;
21
use Yiisoft\Db\Schema\TableSchemaInterface;
22
23
use function array_merge;
24
use function count;
25
use function explode;
26
use function md5;
27
use function preg_match;
28
use function serialize;
29
use function strncasecmp;
30
use function strtolower;
31
use function trim;
32
33
/**
34
 * Implements the SQLite Server specific schema, supporting SQLite 3.3.0 or higher.
35
 *
36
 * @psalm-type Column = array<array-key, array{seqno:string, cid:string, name:string}>
37
 * @psalm-type NormalizePragmaForeignKeyList = array<
38
 *   string,
39
 *   array<
40
 *     array-key,
41
 *     array{
42
 *       id:string,
43
 *       cid:string,
44
 *       seq:string,
45
 *       table:string,
46
 *       from:string,
47
 *       to:string,
48
 *       on_update:string,
49
 *       on_delete:string
50
 *     }
51
 *   >
52
 * >
53
 * @psalm-type PragmaForeignKeyList = array<
54
 *   string,
55
 *   array{
56
 *     id:string,
57
 *     cid:string,
58
 *     seq:string,
59
 *     table:string,
60
 *     from:string,
61
 *     to:string,
62
 *     on_update:string,
63
 *     on_delete:string
64
 *   }
65
 * >
66
 * @psalm-type PragmaIndexInfo = array<array-key, array{seqno:string, cid:string, name:string}>
67
 * @psalm-type PragmaIndexList = array<
68
 *   array-key,
69
 *   array{seq:string, name:string, unique:string, origin:string, partial:string}
70
 * >
71
 * @psalm-type PragmaTableInfo = array<
72
 *   array-key,
73
 *   array{cid:string, name:string, type:string, notnull:string, dflt_value:string|null, pk:string}
74
 * >
75
 */
76
final class Schema extends AbstractPdoSchema
77
{
78
    /**
79
     * @var array Mapping from physical column types (keys) to abstract column types (values).
80
     *
81
     * @psalm-var array<array-key, string> $typeMap
82
     */
83
    private array $typeMap = [
84
        'tinyint' => self::TYPE_TINYINT,
85
        'bit' => self::TYPE_SMALLINT,
86
        'boolean' => self::TYPE_BOOLEAN,
87
        'bool' => self::TYPE_BOOLEAN,
88
        'smallint' => self::TYPE_SMALLINT,
89
        'mediumint' => self::TYPE_INTEGER,
90
        'int' => self::TYPE_INTEGER,
91
        'integer' => self::TYPE_INTEGER,
92
        'bigint' => self::TYPE_BIGINT,
93
        'float' => self::TYPE_FLOAT,
94
        'double' => self::TYPE_DOUBLE,
95
        'real' => self::TYPE_FLOAT,
96
        'decimal' => self::TYPE_DECIMAL,
97
        'numeric' => self::TYPE_DECIMAL,
98
        'tinytext' => self::TYPE_TEXT,
99
        'mediumtext' => self::TYPE_TEXT,
100
        'longtext' => self::TYPE_TEXT,
101
        'text' => self::TYPE_TEXT,
102
        'varchar' => self::TYPE_STRING,
103
        'string' => self::TYPE_STRING,
104
        'char' => self::TYPE_CHAR,
105
        'blob' => self::TYPE_BINARY,
106
        'datetime' => self::TYPE_DATETIME,
107
        'year' => self::TYPE_DATE,
108
        'date' => self::TYPE_DATE,
109
        'time' => self::TYPE_TIME,
110
        'timestamp' => self::TYPE_TIMESTAMP,
111
        'enum' => self::TYPE_STRING,
112
    ];
113
114 15
    public function createColumn(string $type, array|int|string $length = null): ColumnInterface
115
    {
116 15
        return new Column($type, $length);
117
    }
118
119
    /**
120
     * Returns all table names in the database.
121
     *
122
     * This method should be overridden by child classes to support this feature because the default implementation
123
     * simply throws an exception.
124
     *
125
     * @param string $schema The schema of the tables.
126
     * Defaults to empty string, meaning the current or default schema.
127
     *
128
     * @throws Exception
129
     * @throws InvalidConfigException
130
     * @throws Throwable
131
     *
132
     * @return array All tables name in the database. The names have NO schema name prefix.
133
     */
134 10
    protected function findTableNames(string $schema = ''): array
135
    {
136 10
        return $this->db
137 10
           ->createCommand(
138 10
               "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence' ORDER BY tbl_name"
139 10
           )
140 10
           ->queryColumn();
141
    }
142
143
    /**
144
     * Loads the metadata for the specified table.
145
     *
146
     * @param string $name The table name.
147
     *
148
     * @throws Exception
149
     * @throws InvalidArgumentException
150
     * @throws InvalidConfigException
151
     * @throws Throwable
152
     *
153
     * @return TableSchemaInterface|null DBMS-dependent table metadata, `null` if the table doesn't exist.
154
     */
155 155
    protected function loadTableSchema(string $name): TableSchemaInterface|null
156
    {
157 155
        $table = new TableSchema();
158
159 155
        $table->name($name);
160 155
        $table->fullName($name);
161
162 155
        if ($this->findColumns($table)) {
163 112
            $this->findConstraints($table);
164
165 112
            return $table;
166
        }
167
168 65
        return null;
169
    }
170
171
    /**
172
     * Loads a primary key for the given table.
173
     *
174
     * @param string $tableName The table name.
175
     *
176
     * @throws Exception
177
     * @throws InvalidArgumentException
178
     * @throws InvalidConfigException
179
     * @throws Throwable
180
     *
181
     * @return Constraint|null Primary key for the given table, `null` if the table has no primary key.
182
     */
183 49
    protected function loadTablePrimaryKey(string $tableName): Constraint|null
184
    {
185 49
        $tablePrimaryKey = $this->loadTableConstraints($tableName, self::PRIMARY_KEY);
186
187 49
        return $tablePrimaryKey instanceof Constraint ? $tablePrimaryKey : null;
188
    }
189
190
    /**
191
     * Loads all foreign keys for the given table.
192
     *
193
     * @param string $tableName The table name.
194
     *
195
     * @throws Exception
196
     * @throws InvalidConfigException
197
     * @throws Throwable
198
     *
199
     * @return ForeignKeyConstraint[] Foreign keys for the given table.
200
     */
201 9
    protected function loadTableForeignKeys(string $tableName): array
202
    {
203 9
        $result = [];
204
        /** @psalm-var PragmaForeignKeyList $foreignKeysList */
205 9
        $foreignKeysList = $this->getPragmaForeignKeyList($tableName);
206
        /** @psalm-var NormalizePragmaForeignKeyList $foreignKeysList */
207 9
        $foreignKeysList = $this->normalizeRowKeyCase($foreignKeysList, true);
208
        /** @psalm-var NormalizePragmaForeignKeyList $foreignKeysList */
209 9
        $foreignKeysList = DbArrayHelper::index($foreignKeysList, null, ['table']);
210 9
        DbArrayHelper::multisort($foreignKeysList, 'seq');
211
212
        /** @psalm-var NormalizePragmaForeignKeyList $foreignKeysList */
213 9
        foreach ($foreignKeysList as $table => $foreignKey) {
214 5
            $fk = (new ForeignKeyConstraint())
215 5
                ->columnNames(DbArrayHelper::getColumn($foreignKey, 'from'))
216 5
                ->foreignTableName($table)
217 5
                ->foreignColumnNames(DbArrayHelper::getColumn($foreignKey, 'to'))
218 5
                ->onDelete($foreignKey[0]['on_delete'] ?? null)
219 5
                ->onUpdate($foreignKey[0]['on_update'] ?? null);
220
221 5
            $result[] = $fk;
222
        }
223
224 9
        return $result;
225
    }
226
227
    /**
228
     * Loads all indexes for the given table.
229
     *
230
     * @param string $tableName The table name.
231
     *
232
     * @throws Exception
233
     * @throws InvalidArgumentException
234
     * @throws InvalidConfigException
235
     * @throws Throwable
236
     *
237
     * @return array Indexes for the given table.
238
     *
239
     * @psalm-return array|IndexConstraint[]
240
     */
241 14
    protected function loadTableIndexes(string $tableName): array
242
    {
243 14
        $tableIndexes = $this->loadTableConstraints($tableName, self::INDEXES);
244
245 14
        return is_array($tableIndexes) ? $tableIndexes : [];
246
    }
247
248
    /**
249
     * Loads all unique constraints for the given table.
250
     *
251
     * @param string $tableName The table name.
252
     *
253
     * @throws Exception
254
     * @throws InvalidArgumentException
255
     * @throws InvalidConfigException
256
     * @throws Throwable
257
     *
258
     * @return array Unique constraints for the given table.
259
     *
260
     * @psalm-return array|Constraint[]
261
     */
262 15
    protected function loadTableUniques(string $tableName): array
263
    {
264 15
        $tableUniques = $this->loadTableConstraints($tableName, self::UNIQUES);
265
266 15
        return is_array($tableUniques) ? $tableUniques : [];
267
    }
268
269
    /**
270
     * Loads all check constraints for the given table.
271
     *
272
     * @param string $tableName The table name.
273
     *
274
     * @throws Exception
275
     * @throws InvalidArgumentException
276
     * @throws InvalidConfigException
277
     * @throws Throwable
278
     *
279
     * @return CheckConstraint[] Check constraints for the given table.
280
     */
281 15
    protected function loadTableChecks(string $tableName): array
282
    {
283 15
        $sql = $this->db->createCommand(
284 15
            'SELECT `sql` FROM `sqlite_master` WHERE name = :tableName',
285 15
            [':tableName' => $tableName],
286 15
        )->queryScalar();
287
288 15
        $sql = ($sql === false || $sql === null) ? '' : (string) $sql;
289
290
        /** @psalm-var SqlToken[]|SqlToken[][]|SqlToken[][][] $code */
291 15
        $code = (new SqlTokenizer($sql))->tokenize();
292 15
        $pattern = (new SqlTokenizer('any CREATE any TABLE any()'))->tokenize();
293 15
        $result = [];
294
295 15
        if ($code[0] instanceof SqlToken && $code[0]->matches($pattern, 0, $firstMatchIndex, $lastMatchIndex)) {
0 ignored issues
show
Bug introduced by
The method matches() 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

295
        if ($code[0] instanceof SqlToken && $code[0]->/** @scrutinizer ignore-call */ matches($pattern, 0, $firstMatchIndex, $lastMatchIndex)) {

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...
296 15
            $offset = 0;
297 15
            $createTableToken = $code[0][(int) $lastMatchIndex - 1];
298 15
            $sqlTokenizerAnyCheck = new SqlTokenizer('any CHECK()');
299
300
            while (
301 15
                $createTableToken instanceof SqlToken &&
302 15
                $createTableToken->matches($sqlTokenizerAnyCheck->tokenize(), (int) $offset, $firstMatchIndex, $offset)
303
            ) {
304 4
                $name = null;
305 4
                $checkSql = (string) $createTableToken[(int) $offset - 1];
306 4
                $pattern = (new SqlTokenizer('CONSTRAINT any'))->tokenize();
307
308
                if (
309 4
                    isset($createTableToken[(int) $firstMatchIndex - 2])
310 4
                    && $createTableToken->matches($pattern, (int) $firstMatchIndex - 2)
311
                ) {
312 1
                    $sqlToken = $createTableToken[(int) $firstMatchIndex - 1];
313 1
                    $name = $sqlToken?->getContent();
314
                }
315
316 4
                $result[] = (new CheckConstraint())->name($name)->expression($checkSql);
317
            }
318
        }
319
320 15
        return $result;
321
    }
322
323
    /**
324
     * Loads all default value constraints for the given table.
325
     *
326
     * @param string $tableName The table name.
327
     *
328
     * @throws NotSupportedException
329
     *
330
     * @return array Default value constraints for the given table.
331
     */
332 13
    protected function loadTableDefaultValues(string $tableName): array
333
    {
334 13
        throw new NotSupportedException('SQLite does not support default value constraints.');
335
    }
336
337
    /**
338
     * Collects the table column metadata.
339
     *
340
     * @param TableSchemaInterface $table The table metadata.
341
     *
342
     * @throws Exception
343
     * @throws InvalidConfigException
344
     * @throws Throwable
345
     *
346
     * @return bool Whether the table exists in the database.
347
     */
348 155
    protected function findColumns(TableSchemaInterface $table): bool
349
    {
350
        /** @psalm-var PragmaTableInfo $columns */
351 155
        $columns = $this->getPragmaTableInfo($table->getName());
352
353 155
        foreach ($columns as $info) {
354 112
            $column = $this->loadColumnSchema($info);
355 112
            $table->column($column->getName(), $column);
356
357 112
            if ($column->isPrimaryKey()) {
358 75
                $table->primaryKey($column->getName());
359
            }
360
        }
361
362 155
        $column = count($table->getPrimaryKey()) === 1 ? $table->getColumn($table->getPrimaryKey()[0]) : null;
363
364 155
        if ($column !== null && !strncasecmp($column->getDbType() ?? '', 'int', 3)) {
365 70
            $table->sequenceName('');
366 70
            $column->autoIncrement(true);
367
        }
368
369 155
        return !empty($columns);
370
    }
371
372
    /**
373
     * Collects the foreign key column details for the given table.
374
     *
375
     * @param TableSchemaInterface $table The table metadata.
376
     *
377
     * @throws Exception
378
     * @throws InvalidConfigException
379
     * @throws Throwable
380
     */
381 112
    protected function findConstraints(TableSchemaInterface $table): void
382
    {
383
        /** @psalm-var PragmaForeignKeyList $foreignKeysList */
384 112
        $foreignKeysList = $this->getPragmaForeignKeyList($table->getName());
385
386 112
        foreach ($foreignKeysList as $foreignKey) {
387 5
            $id = (int) $foreignKey['id'];
388 5
            $fk = $table->getForeignKeys();
389
390 5
            if (!isset($fk[$id])) {
391 5
                $table->foreignKey($id, [$foreignKey['table'], $foreignKey['from'] => $foreignKey['to']]);
392
            } else {
393
                /** composite FK */
394 5
                $table->compositeForeignKey($id, $foreignKey['from'], $foreignKey['to']);
395
            }
396
        }
397
    }
398
399
    /**
400
     * Returns all unique indexes for the given table.
401
     *
402
     * Each array element is of the following structure:
403
     *
404
     * ```php
405
     * [
406
     *     'IndexName1' => ['col1' [, ...]],
407
     *     'IndexName2' => ['col2' [, ...]],
408
     * ]
409
     * ```
410
     *
411
     * @param TableSchemaInterface $table The table metadata.
412
     *
413
     * @throws Exception
414
     * @throws InvalidConfigException
415
     * @throws Throwable
416
     *
417
     * @return array All unique indexes for the given table.
418
     */
419 1
    public function findUniqueIndexes(TableSchemaInterface $table): array
420
    {
421
        /** @psalm-var PragmaIndexList $indexList */
422 1
        $indexList = $this->getPragmaIndexList($table->getName());
423 1
        $uniqueIndexes = [];
424
425 1
        foreach ($indexList as $index) {
426 1
            $indexName = $index['name'];
427
            /** @psalm-var PragmaIndexInfo $indexInfo */
428 1
            $indexInfo = $this->getPragmaIndexInfo($index['name']);
429
430 1
            if ($index['unique']) {
431 1
                $uniqueIndexes[$indexName] = [];
432 1
                foreach ($indexInfo as $row) {
433 1
                    $uniqueIndexes[$indexName][] = $row['name'];
434
                }
435
            }
436
        }
437
438 1
        return $uniqueIndexes;
439
    }
440
441
    /**
442
     * @throws NotSupportedException
443
     */
444 1
    public function getSchemaDefaultValues(string $schema = '', bool $refresh = false): array
445
    {
446 1
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
447
    }
448
449
    /**
450
     * Loads the column information into a {@see ColumnSchemaInterface} object.
451
     *
452
     * @param array $info The column information.
453
     *
454
     * @return ColumnSchemaInterface The column schema object.
455
     *
456
     * @psalm-param array{cid:string, name:string, type:string, notnull:string, dflt_value:string|null, pk:string} $info
457
     */
458 112
    protected function loadColumnSchema(array $info): ColumnSchemaInterface
459
    {
460 112
        $column = $this->createColumnSchema($info['name']);
461 112
        $column->allowNull(!$info['notnull']);
462 112
        $column->primaryKey($info['pk'] != '0');
463 112
        $column->dbType(strtolower($info['type']));
464 112
        $column->unsigned(str_contains($column->getDbType() ?? '', 'unsigned'));
465 112
        $column->type(self::TYPE_STRING);
466
467 112
        if (preg_match('/^(\w+)(?:\(([^)]+)\))?/', $column->getDbType() ?? '', $matches)) {
468 112
            $type = strtolower($matches[1]);
469
470 112
            if (isset($this->typeMap[$type])) {
471 112
                $column->type($this->typeMap[$type]);
472
            }
473
474 112
            if (!empty($matches[2])) {
475 98
                $values = explode(',', $matches[2]);
476 98
                $column->precision((int) $values[0]);
477 98
                $column->size((int) $values[0]);
478
479 98
                if (isset($values[1])) {
480 30
                    $column->scale((int) $values[1]);
481
                }
482
483 98
                if (($type === 'tinyint' || $type === 'bit') && $column->getSize() === 1) {
484 24
                    $column->type(self::TYPE_BOOLEAN);
485 98
                } elseif ($type === 'bit') {
486 4
                    if ($column->getSize() > 32) {
487 4
                        $column->type(self::TYPE_BIGINT);
488 4
                    } elseif ($column->getSize() === 32) {
489 4
                        $column->type(self::TYPE_INTEGER);
490
                    }
491
                }
492
            }
493
        }
494
495 112
        $column->phpType($this->getColumnPhpType($column));
496 112
        $column->defaultValue($this->normalizeDefaultValue($info['dflt_value'], $column));
497
498 112
        return $column;
499
    }
500
501
    /**
502
     * Converts column's default value according to {@see ColumnSchema::phpType} after retrieval from the database.
503
     *
504
     * @param string|null $defaultValue The default value retrieved from the database.
505
     * @param ColumnSchemaInterface $column The column schema object.
506
     *
507
     * @return mixed The normalized default value.
508
     *
509
     * @psalm-suppress PossiblyNullArgument
510
     */
511 112
    private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterface $column): mixed
512
    {
513 112
        if ($column->isPrimaryKey()) {
514 75
            return null;
515
        }
516
517 107
        return match ($defaultValue) {
518 107
            null, 'null', '' => null,
519 107
            'CURRENT_TIMESTAMP', 'CURRENT_DATE', 'CURRENT_TIME' => new Expression($defaultValue),
0 ignored issues
show
Bug introduced by
It seems like $defaultValue can also be of type null; however, parameter $expression of Yiisoft\Db\Expression\Expression::__construct() does only seem to accept string, 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

519
            'CURRENT_TIMESTAMP', 'CURRENT_DATE', 'CURRENT_TIME' => new Expression(/** @scrutinizer ignore-type */ $defaultValue),
Loading history...
520 107
            default => $column->phpTypecast(trim($defaultValue, "'\"")),
0 ignored issues
show
Bug introduced by
It seems like $defaultValue can also be of type null; however, parameter $string of trim() does only seem to accept string, 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

520
            default => $column->phpTypecast(trim(/** @scrutinizer ignore-type */ $defaultValue, "'\"")),
Loading history...
521 107
        };
522
    }
523
524
    /**
525
     * Returns table columns info.
526
     *
527
     * @param string $tableName The table name.
528
     *
529
     * @throws Exception
530
     * @throws InvalidConfigException
531
     * @throws Throwable
532
     *
533
     * @return array The table columns info.
534
     */
535 54
    private function loadTableColumnsInfo(string $tableName): array
536
    {
537 54
        $tableColumns = $this->getPragmaTableInfo($tableName);
538
        /** @psalm-var PragmaTableInfo $tableColumns */
539 54
        $tableColumns = $this->normalizeRowKeyCase($tableColumns, true);
540
541 54
        return DbArrayHelper::index($tableColumns, 'cid');
542
    }
543
544
    /**
545
     * Loads multiple types of constraints and returns the specified ones.
546
     *
547
     * @param string $tableName The table name.
548
     * @param string $returnType Return type: (primaryKey, indexes, uniques).
549
     *
550
     * @throws Exception
551
     * @throws InvalidConfigException
552
     * @throws Throwable
553
     *
554
     * @psalm-return Constraint[]|IndexConstraint[]|Constraint|null
555
     */
556 78
    private function loadTableConstraints(string $tableName, string $returnType): Constraint|array|null
557
    {
558 78
        $indexList = $this->getPragmaIndexList($tableName);
559
        /** @psalm-var PragmaIndexList $indexes */
560 78
        $indexes = $this->normalizeRowKeyCase($indexList, true);
561 78
        $result = [
562 78
            self::PRIMARY_KEY => null,
563 78
            self::INDEXES => [],
564 78
            self::UNIQUES => [],
565 78
        ];
566
567 78
        foreach ($indexes as $index) {
568
            /** @psalm-var Column $columns */
569 59
            $columns = $this->getPragmaIndexInfo($index['name']);
570
571 59
            if ($index['origin'] === 'pk') {
572 24
                $result[self::PRIMARY_KEY] = (new Constraint())
573 24
                    ->columnNames(DbArrayHelper::getColumn($columns, 'name'));
574
            }
575
576 59
            if ($index['origin'] === 'u') {
577 54
                $result[self::UNIQUES][] = (new Constraint())
578 54
                    ->name($index['name'])
579 54
                    ->columnNames(DbArrayHelper::getColumn($columns, 'name'));
580
            }
581
582 59
            $result[self::INDEXES][] = (new IndexConstraint())
583 59
                ->primary($index['origin'] === 'pk')
584 59
                ->unique((bool) $index['unique'])
585 59
                ->name($index['name'])
586 59
                ->columnNames(DbArrayHelper::getColumn($columns, 'name'));
587
        }
588
589 78
        if (!isset($result[self::PRIMARY_KEY])) {
590
            /**
591
             * Extra check for PK in case of `INTEGER PRIMARY KEY` with ROWID.
592
             *
593
             * @link https://www.sqlite.org/lang_createtable.html#primkeyconst
594
             *
595
             * @psalm-var PragmaTableInfo $tableColumns
596
             */
597 54
            $tableColumns = $this->loadTableColumnsInfo($tableName);
598
599 54
            foreach ($tableColumns as $tableColumn) {
600 54
                if ($tableColumn['pk'] > 0) {
601 34
                    $result[self::PRIMARY_KEY] = (new Constraint())->columnNames([$tableColumn['name']]);
602 34
                    break;
603
                }
604
            }
605
        }
606
607 78
        foreach ($result as $type => $data) {
608 78
            $this->setTableMetadata($tableName, $type, $data);
609
        }
610
611 78
        return $result[$returnType];
612
    }
613
614
    /**
615
     * Creates a column schema for the database.
616
     *
617
     * This method may be overridden by child classes to create a DBMS-specific column schema.
618
     *
619
     * @param string $name Name of the column.
620
     */
621 112
    private function createColumnSchema(string $name): ColumnSchemaInterface
622
    {
623 112
        return new ColumnSchema($name);
624
    }
625
626
    /**
627
     * @throws Exception
628
     * @throws InvalidConfigException
629
     * @throws Throwable
630
     */
631 121
    private function getPragmaForeignKeyList(string $tableName): array
632
    {
633 121
        return $this->db->createCommand(
634 121
            'PRAGMA FOREIGN_KEY_LIST(' . $this->db->getQuoter()->quoteSimpleTableName($tableName) . ')'
635 121
        )->queryAll();
636
    }
637
638
    /**
639
     * @throws Exception
640
     * @throws InvalidConfigException
641
     * @throws Throwable
642
     */
643 60
    private function getPragmaIndexInfo(string $name): array
644
    {
645 60
        $column = $this->db
646 60
            ->createCommand('PRAGMA INDEX_INFO(' . (string) $this->db->getQuoter()->quoteValue($name) . ')')
647 60
            ->queryAll();
648
        /** @psalm-var Column $column */
649 60
        $column = $this->normalizeRowKeyCase($column, true);
650 60
        DbArrayHelper::multisort($column, 'seqno');
651
652 60
        return $column;
653
    }
654
655
    /**
656
     * @throws Exception
657
     * @throws InvalidConfigException
658
     * @throws Throwable
659
     */
660 79
    private function getPragmaIndexList(string $tableName): array
661
    {
662 79
        return $this->db
663 79
            ->createCommand('PRAGMA INDEX_LIST(' . (string) $this->db->getQuoter()->quoteValue($tableName) . ')')
664 79
            ->queryAll();
665
    }
666
667
    /**
668
     * @throws Exception
669
     * @throws InvalidConfigException
670
     * @throws Throwable
671
     */
672 164
    private function getPragmaTableInfo(string $tableName): array
673
    {
674 164
        return $this->db->createCommand(
675 164
            'PRAGMA TABLE_INFO(' . $this->db->getQuoter()->quoteSimpleTableName($tableName) . ')'
676 164
        )->queryAll();
677
    }
678
679
    /**
680
     * @throws Exception
681
     * @throws InvalidConfigException
682
     * @throws Throwable
683
     */
684 1
    protected function findViewNames(string $schema = ''): array
685
    {
686
        /** @psalm-var string[][] $views */
687 1
        $views = $this->db->createCommand(
688 1
            <<<SQL
689
            SELECT name as view FROM sqlite_master WHERE type = 'view' AND name NOT LIKE 'sqlite_%'
690 1
            SQL,
691 1
        )->queryAll();
692
693 1
        foreach ($views as $key => $view) {
694 1
            $views[$key] = $view['view'];
695
        }
696
697 1
        return $views;
698
    }
699
700
    /**
701
     * Returns the cache key for the specified table name.
702
     *
703
     * @param string $name the table name.
704
     *
705
     * @return array The cache key.
706
     */
707 222
    protected function getCacheKey(string $name): array
708
    {
709 222
        return array_merge([self::class], $this->generateCacheKey(), [$this->getRawTableName($name)]);
710
    }
711
712
    /**
713
     * Returns the cache tag name.
714
     *
715
     * This allows {@see refresh()} to invalidate all cached table schemas.
716
     *
717
     * @return string The cache tag name.
718
     */
719 206
    protected function getCacheTag(): string
720
    {
721 206
        return md5(serialize(array_merge([self::class], $this->generateCacheKey())));
722
    }
723
}
724