Test Failed
Pull Request — master (#221)
by Def
05:07 queued 01:30
created

Schema::getPragmaIndexInfo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
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 6
cts 6
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\Exception\Exception;
13
use Yiisoft\Db\Exception\InvalidArgumentException;
14
use Yiisoft\Db\Exception\InvalidConfigException;
15
use Yiisoft\Db\Exception\NotSupportedException;
16
use Yiisoft\Db\Expression\Expression;
17
use Yiisoft\Db\Helper\ArrayHelper;
18
use Yiisoft\Db\Schema\AbstractSchema;
19
use Yiisoft\Db\Schema\Builder\ColumnInterface;
0 ignored issues
show
Bug introduced by
The type Yiisoft\Db\Schema\Builder\ColumnInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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

300
        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...
301 15
            $offset = 0;
302 15
            $createTableToken = $code[0][(int) $lastMatchIndex - 1];
303
            $sqlTokenizerAnyCheck = new SqlTokenizer('any CHECK()');
304 4
305 4
            while (
306 4
                $createTableToken instanceof SqlToken &&
307
                $createTableToken->matches($sqlTokenizerAnyCheck->tokenize(), (int) $offset, $firstMatchIndex, $offset)
308
            ) {
309 4
                $name = null;
310 4
                $checkSql = (string) $createTableToken[(int) $offset - 1];
311
                $pattern = (new SqlTokenizer('CONSTRAINT any'))->tokenize();
312 1
313 1
                if (
314
                    isset($createTableToken[(int) $firstMatchIndex - 2])
315
                    && $createTableToken->matches($pattern, (int) $firstMatchIndex - 2)
316 4
                ) {
317
                    $sqlToken = $createTableToken[(int) $firstMatchIndex - 1];
318
                    $name = $sqlToken?->getContent();
319
                }
320 15
321
                $result[] = (new CheckConstraint())->name($name)->expression($checkSql);
322
            }
323
        }
324
325
        return $result;
326
    }
327
328
    /**
329
     * Loads all default value constraints for the given table.
330
     *
331
     * @param string $tableName The table name.
332 13
     *
333
     * @throws NotSupportedException
334 13
     *
335
     * @return array Default value constraints for the given table.
336
     */
337 7
    protected function loadTableDefaultValues(string $tableName): array
338
    {
339
        throw new NotSupportedException('SQLite does not support default value constraints.');
340
    }
341 7
342
    /**
343
     * Collects the table column metadata.
344
     *
345
     * @param TableSchemaInterface $table The table metadata.
346
     *
347
     * @throws Exception
348
     * @throws InvalidConfigException
349
     * @throws Throwable
350
     *
351
     * @return bool Whether the table exists in the database.
352
     */
353
    protected function findColumns(TableSchemaInterface $table): bool
354
    {
355 146
        /** @psalm-var PragmaTableInfo $columns */
356
        $columns = $this->getPragmaTableInfo($table->getName());
357
358 146
        foreach ($columns as $info) {
359
            $column = $this->loadColumnSchema($info);
360 146
            $table->columns($column->getName(), $column);
361 103
362 103
            if ($column->isPrimaryKey()) {
363
                $table->primaryKey($column->getName());
364 103
            }
365 70
        }
366
367
        $column = count($table->getPrimaryKey()) === 1 ? $table->getColumn($table->getPrimaryKey()[0]) : null;
368
369 146
        if ($column !== null && !strncasecmp($column->getDbType(), 'int', 3)) {
370
            $table->sequenceName('');
371 146
            $column->autoIncrement(true);
372 69
        }
373 69
374
        return !empty($columns);
375
    }
376 146
377
    /**
378
     * Collects the foreign key column details for the given table.
379
     *
380
     * @param TableSchemaInterface $table The table metadata.
381
     *
382
     * @throws Exception
383
     * @throws InvalidConfigException
384
     * @throws Throwable
385
     */
386
    protected function findConstraints(TableSchemaInterface $table): void
387
    {
388 103
        /** @psalm-var PragmaForeignKeyList $foreignKeysList */
389
        $foreignKeysList = $this->getPragmaForeignKeyList($table->getName());
390
391 103
        foreach ($foreignKeysList as $foreignKey) {
392
            $id = (int) $foreignKey['id'];
393 103
            $fk = $table->getForeignKeys();
394 5
395 5
            if (!isset($fk[$id])) {
396
                $table->foreignKey($id, ([$foreignKey['table'], $foreignKey['from'] => $foreignKey['to']]));
397 5
            } else {
398 5
                /** composite FK */
399
                $table->compositeFK($id, $foreignKey['from'], $foreignKey['to']);
400
            }
401 5
        }
402
    }
403
404
    /**
405
     * Returns all unique indexes for the given table.
406
     *
407
     * Each array element is of the following structure:
408
     *
409
     * ```php
410
     * [
411
     *     'IndexName1' => ['col1' [, ...]],
412
     *     'IndexName2' => ['col2' [, ...]],
413
     * ]
414
     * ```
415
     *
416
     * @param TableSchemaInterface $table The table metadata.
417
     *
418
     * @throws Exception
419
     * @throws InvalidConfigException
420
     * @throws Throwable
421
     *
422
     * @return array All unique indexes for the given table.
423
     */
424
    public function findUniqueIndexes(TableSchemaInterface $table): array
425
    {
426 1
        /** @psalm-var PragmaIndexList $indexList */
427
        $indexList = $this->getPragmaIndexList($table->getName());
428
        $uniqueIndexes = [];
429 1
430 1
        foreach ($indexList as $index) {
431
            $indexName = $index['name'];
432 1
            /** @psalm-var PragmaIndexInfo $indexInfo */
433 1
            $indexInfo = $this->getPragmaIndexInfo($index['name']);
434
435 1
            if ($index['unique']) {
436
                $uniqueIndexes[$indexName] = [];
437 1
                foreach ($indexInfo as $row) {
438 1
                    $uniqueIndexes[$indexName][] = $row['name'];
439 1
                }
440 1
            }
441
        }
442
443
        return $uniqueIndexes;
444
    }
445 1
446
    /**
447
     * @throws NotSupportedException
448
     */
449
    public function getSchemaDefaultValues(string $schema = '', bool $refresh = false): array
450
    {
451 1
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
452
    }
453 1
454
    /**
455
     * Loads the column information into a {@see ColumnSchemaInterface} object.
456
     *
457
     * @param array $info The column information.
458
     *
459
     * @return ColumnSchemaInterface The column schema object.
460
     *
461
     * @psalm-param array{cid:string, name:string, type:string, notnull:string, dflt_value:string|null, pk:string} $info
462
     */
463
    protected function loadColumnSchema(array $info): ColumnSchemaInterface
464
    {
465 103
        $column = $this->createColumnSchema();
466
        $column->name($info['name']);
467 103
        $column->allowNull(!$info['notnull']);
468 103
        $column->primaryKey($info['pk'] != '0');
469 103
        $column->dbType(strtolower($info['type']));
470 103
        $column->unsigned(str_contains($column->getDbType(), 'unsigned'));
471 103
        $column->type(self::TYPE_STRING);
472 103
473 103
        if (preg_match('/^(\w+)(?:\(([^)]+)\))?/', $column->getDbType(), $matches)) {
474
            $type = strtolower($matches[1]);
475 103
476 103
            if (isset($this->typeMap[$type])) {
477
                $column->type($this->typeMap[$type]);
478 103
            }
479 103
480
            if (!empty($matches[2])) {
481
                $values = explode(',', $matches[2]);
482 103
                $column->precision((int) $values[0]);
483 91
                $column->size((int) $values[0]);
484 91
485 91
                if (isset($values[1])) {
486
                    $column->scale((int) $values[1]);
487 91
                }
488 30
489
                if ($column->getSize() === 1 && ($type === 'tinyint' || $type === 'bit')) {
490
                    $column->type(self::TYPE_BOOLEAN);
491 91
                } elseif ($type === 'bit') {
492 24
                    if ($column->getSize() > 32) {
493 91
                        $column->type(self::TYPE_BIGINT);
494 4
                    } elseif ($column->getSize() === 32) {
495 4
                        $column->type(self::TYPE_INTEGER);
496 4
                    }
497 4
                }
498
            }
499
        }
500
501
        $column->phpType($this->getColumnPhpType($column));
502
503 103
        if (!$column->isPrimaryKey()) {
504
            if ($info['dflt_value'] === 'null' || $info['dflt_value'] === '' || $info['dflt_value'] === null) {
505 103
                $column->defaultValue(null);
506 101
            } elseif ($column->getType() === 'timestamp' && $info['dflt_value'] === 'CURRENT_TIMESTAMP') {
507 98
                $column->defaultValue(new Expression('CURRENT_TIMESTAMP'));
508 73
            } else {
509 23
                $value = trim($info['dflt_value'], "'\"");
510
                $column->defaultValue($column->phpTypecast($value));
511 73
            }
512 73
        }
513
514
        return $column;
515
    }
516 103
517
    /**
518
     * Returns table columns info.
519
     *
520
     * @param string $tableName The table name.
521
     *
522
     * @throws Exception
523
     * @throws InvalidConfigException
524
     * @throws Throwable
525
     *
526
     * @return array The table columns info.
527
     */
528
    private function loadTableColumnsInfo(string $tableName): array
529
    {
530 54
        $tableColumns = $this->getPragmaTableInfo($tableName);
531
        /** @psalm-var PragmaTableInfo $tableColumns */
532 54
        $tableColumns = $this->normalizeRowKeyCase($tableColumns, true);
533
534 54
        return ArrayHelper::index($tableColumns, 'cid');
535
    }
536 54
537
    /**
538
     * Loads multiple types of constraints and returns the specified ones.
539
     *
540
     * @param string $tableName The table name.
541
     * @param string $returnType Return type: (primaryKey, indexes, uniques).
542
     *
543
     * @throws Exception
544
     * @throws InvalidConfigException
545
     * @throws Throwable
546
     *
547
     * @psalm-return Constraint[]|IndexConstraint[]|Constraint|null
548
     */
549
    private function loadTableConstraints(string $tableName, string $returnType): Constraint|array|null
550
    {
551 78
        $indexList = $this->getPragmaIndexList($tableName);
552
        /** @psalm-var PragmaIndexList $indexes */
553 78
        $indexes = $this->normalizeRowKeyCase($indexList, true);
554
        $result = [
555 78
            self::PRIMARY_KEY => null,
556 78
            self::INDEXES => [],
557 78
            self::UNIQUES => [],
558 78
        ];
559 78
560 78
        foreach ($indexes as $index) {
561
            /** @psalm-var Column $columns */
562 78
            $columns = $this->getPragmaIndexInfo($index['name']);
563
564 59
            if ($index['origin'] === 'pk') {
565
                $result[self::PRIMARY_KEY] = (new Constraint())
566 59
                    ->columnNames(ArrayHelper::getColumn($columns, 'name'));
567 24
            }
568 24
569
            if ($index['origin'] === 'u') {
570
                $result[self::UNIQUES][] = (new Constraint())
571 59
                    ->name($index['name'])
572 54
                    ->columnNames(ArrayHelper::getColumn($columns, 'name'));
573 54
            }
574 54
575
            $result[self::INDEXES][] = (new IndexConstraint())
576
                ->primary($index['origin'] === 'pk')
577 59
                ->unique((bool) $index['unique'])
578 59
                ->name($index['name'])
579 59
                ->columnNames(ArrayHelper::getColumn($columns, 'name'));
580 59
        }
581 59
582
        if (!isset($result[self::PRIMARY_KEY])) {
583
            /**
584 78
             * Additional check for PK in case of INTEGER PRIMARY KEY with ROWID.
585
             *
586
             * @link https://www.sqlite.org/lang_createtable.html#primkeyconst
587
             *
588
             * @psalm-var PragmaTableInfo $tableColumns
589
             */
590
            $tableColumns = $this->loadTableColumnsInfo($tableName);
591
592 54
            foreach ($tableColumns as $tableColumn) {
593
                if ($tableColumn['pk'] > 0) {
594 54
                    $result[self::PRIMARY_KEY] = (new Constraint())->columnNames([$tableColumn['name']]);
595 54
                    break;
596 34
                }
597 34
            }
598
        }
599
600
        foreach ($result as $type => $data) {
601
            $this->setTableMetadata($tableName, $type, $data);
602 78
        }
603 78
604
        return $result[$returnType];
605
    }
606 78
607
    /**
608
     * Creates a column schema for the database.
609
     *
610
     * This method may be overridden by child classes to create a DBMS-specific column schema.
611
     */
612
    private function createColumnSchema(): ColumnSchemaInterface
613
    {
614 103
        return new ColumnSchema();
615
    }
616 103
617
    /**
618
     * @throws Exception
619
     * @throws InvalidConfigException
620
     * @throws Throwable
621
     */
622
    private function getPragmaForeignKeyList(string $tableName): array
623
    {
624 112
        return $this->db->createCommand(
625
            'PRAGMA FOREIGN_KEY_LIST(' . $this->db->getQuoter()->quoteSimpleTableName(($tableName)) . ')'
626 112
        )->queryAll();
627 112
    }
628 112
629
    /**
630
     * @throws Exception
631
     * @throws InvalidConfigException
632
     * @throws Throwable
633
     */
634
    private function getPragmaIndexInfo(string $name): array
635
    {
636 60
        $column = $this->db
637
            ->createCommand('PRAGMA INDEX_INFO(' . (string) $this->db->getQuoter()->quoteValue($name) . ')')
638 60
            ->queryAll();
639 60
        /** @psalm-var Column $column */
640 60
        $column = $this->normalizeRowKeyCase($column, true);
641
        ArrayHelper::multisort($column, 'seqno');
642 60
643 60
        return $column;
644
    }
645 60
646
    /**
647
     * @throws Exception
648
     * @throws InvalidConfigException
649
     * @throws Throwable
650
     */
651
    private function getPragmaIndexList(string $tableName): array
652
    {
653 79
        return $this->db
654
            ->createCommand('PRAGMA INDEX_LIST(' . (string) $this->db->getQuoter()->quoteValue($tableName) . ')')
655 79
            ->queryAll();
656 79
    }
657 79
658
    /**
659
     * @throws Exception
660
     * @throws InvalidConfigException
661
     * @throws Throwable
662
     */
663
    private function getPragmaTableInfo(string $tableName): array
664
    {
665 155
        return $this->db->createCommand(
666
            'PRAGMA TABLE_INFO(' . $this->db->getQuoter()->quoteSimpleTableName($tableName) . ')'
667 155
        )->queryAll();
668 155
    }
669 155
670
    /**
671
     * @throws Exception
672
     * @throws InvalidConfigException
673
     * @throws Throwable
674
     */
675
    protected function findViewNames(string $schema = ''): array
676
    {
677 1
        /** @psalm-var string[][] $views */
678
        $views = $this->db->createCommand(
679
            <<<SQL
680 1
            SELECT name as view FROM sqlite_master WHERE type = 'view' AND name NOT LIKE 'sqlite_%'
681 1
            SQL,
682
        )->queryAll();
683 1
684 1
        foreach ($views as $key => $view) {
685
            $views[$key] = $view['view'];
686 1
        }
687 1
688
        return $views;
689
    }
690 1
691
    /**
692
     * Returns the cache key for the specified table name.
693
     *
694
     * @param string $name the table name.
695
     *
696
     * @return array The cache key.
697
     */
698
    protected function getCacheKey(string $name): array
699
    {
700 212
        return array_merge([self::class], $this->db->getCacheKey(), [$this->getRawTableName($name)]);
701
    }
702 212
703
    /**
704
     * Returns the cache tag name.
705
     *
706
     * This allows {@see refresh()} to invalidate all cached table schemas.
707
     *
708
     * @return string The cache tag name.
709
     */
710
    protected function getCacheTag(): string
711
    {
712 213
        return md5(serialize(array_merge([self::class], $this->db->getCacheKey())));
713
    }
714
}
715