Passed
Pull Request — master (#268)
by
unknown
03:51
created

Schema::createColumn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

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

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